From e7f0fec0cf49acefbbfe3f87990814b4b33558c4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 21:00:30 +1000 Subject: [PATCH 01/70] Adds models representing a purchase order and a purchase order line item --- lib/inventree/purchase_order.dart | 116 ++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 lib/inventree/purchase_order.dart diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart new file mode 100644 index 00000000..54a2cb9c --- /dev/null +++ b/lib/inventree/purchase_order.dart @@ -0,0 +1,116 @@ +import 'model.dart'; + +// TODO: In the future, status codes should be retrieved from the server +const int PO_STATUS_PENDING = 10; +const int PO_STATUS_PLACED = 20; +const int PO_STATUS_COMPLETE = 30; +const int PO_STATUS_CANCELLED = 40; +const int PO_STATUS_LOST = 50; +const int PO_STATUS_RETURNED = 60; + +class InvenTreePO extends InvenTreeModel { + + @override + String get URL => "order/po/"; + + @override + Map formFields() { + return { + "reference": {}, + "supplier": {}, + "supplier_reference": {}, + "description": {}, + "target_date": {}, + "link": {}, + "responsible": {}, + }; + } + + InvenTreePO() : super(); + + String get issueDate => jsondata['issue_date'] ?? ""; + + String get completeDate => jsondata['complete_date'] ?? ""; + + String get creationDate => jsondata['creation_date'] ?? ""; + + String get targetDate => jsondata['target_date'] ?? ""; + + int get lineItems => jsondata['line_items'] ?? 0; + + bool get overdue => jsondata['overdue'] ?? false; + + String get reference => jsondata['reference'] ?? ""; + + int get responsible => jsondata['responsible'] ?? -1; + + int get supplier => jsondata['supplier'] ?? -1; + + String get supplierReference => jsondata['supplier_reference'] ?? ""; + + int get status => jsondata['status'] ?? -1; + + String get statusText => jsondata['status_text'] ?? ""; + + bool get isOpen => this.status == PO_STATUS_PENDING || this.status == PO_STATUS_PLACED; + + bool get isFailed => this.status == PO_STATUS_CANCELLED || this.status == PO_STATUS_LOST || this.status == PO_STATUS_RETURNED; + + InvenTreePO.fromJson(Map json) : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreePO.fromJson(json); + } +} + +class InvenTreePOLineItem extends InvenTreeModel { + @override + String get URL => "order/po-line/"; + + @override + Map formFields() { + return { + // TODO: @Guusggg Not sure what will come here. + // "quantity": {}, + // "reference": {}, + // "notes": {}, + // "order": {}, + // "part": {}, + "received": {}, + // "purchase_price": {}, + // "purchase_price_currency": {}, + // "destination": {} + }; + } + + double get quantity => jsondata['quantity'] ?? 0; + + double get received => jsondata['received'] ?? 0; + + String get reference => jsondata['reference'] ?? ""; + + int get order => jsondata['order'] ?? -1; + + int get part => jsondata['part'] ?? -1; + + double get purchasePrice => double.parse(jsondata['purchase_price']); + + String get purchasePriceCurrency => jsondata['purchase_price_currency'] ?? ""; + + String get purchasePriceString => jsondata['purchase_price_string'] ?? ""; + + int get destination => jsondata['destination'] ?? -1; + + Map get destinationDetail => jsondata['destination_detail']; + + InvenTreePOLineItem() : super(); + + InvenTreePOLineItem.fromJson(Map json) + : super.fromJson(json); + + @override + InvenTreeModel createFromJson(Map json) { + return InvenTreePOLineItem.fromJson(json); + } +} From f9b688cdabe32c0c3a9286f7a2df8ac63aeb87ac Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 21:08:31 +1000 Subject: [PATCH 02/70] Extract part ID from supplier part --- lib/inventree/company.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index da34f14a..aea0325c 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -101,6 +101,8 @@ class InvenTreeSupplierPart extends InvenTreeModel { String get MPN => jsondata['MPN'] ?? ''; + int get partId => jsondata['part'] ?? -1; + @override InvenTreeModel createFromJson(Map json) { var part = InvenTreeSupplierPart.fromJson(json); From 11c8a5567750533d41898e17979a365470981b81 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 22:34:55 +1000 Subject: [PATCH 03/70] Refactor the "home" page - Use a grid view instead of hard-coded columns - Aware of the screen orientation --- lib/widget/home.dart | 294 ++++++++++++++++++++----------------------- 1 file changed, 133 insertions(+), 161 deletions(-) diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 08c3a5ef..39991a1d 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -183,6 +183,27 @@ class _InvenTreeHomePageState extends State { } } + Widget _iconButton(String label, IconData icon, {Function()? callback}) { + + return GestureDetector( + child: Card( + margin: EdgeInsets.symmetric( + vertical: 10, + horizontal: 10 + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FaIcon(icon), + Text(label), + ] + ) + ), + onTap: callback, + ); + + } + @override Widget build(BuildContext context) { @@ -207,184 +228,135 @@ class _InvenTreeHomePageState extends State { ], ), drawer: new InvenTreeDrawer(context), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: ([ - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.barcode), - tooltip: L10().scanBarcode, - onPressed: () { _scan(context); }, - ), - Text(L10().scanBarcode), - ], - ), - ], + body: GridView.count( + crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait ? 3 : 5, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + children: [ + _iconButton( + L10().scanBarcode, + FontAwesomeIcons.barcode, + callback: () { + _scan(context); + } ), - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.shapes), - tooltip: L10().parts, - onPressed: () { _parts(context); }, - ), - Text(L10().parts), - ], - ), - Column( - children: [ - - IconButton( - icon: new FaIcon(FontAwesomeIcons.search), - tooltip: L10().searchParts, - onPressed: _searchParts, - ), - Text(L10().searchParts), - ], - ), - // TODO - Re-add starred parts link - /* - Column( - children: [ - IconButton( - icon: FaIcon(FontAwesomeIcons.solidStar), - onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); - }, - ), - Text("Starred Parts"), - ] - ), - */ - ], + _iconButton( + L10().parts, + FontAwesomeIcons.shapes, + callback: () { + _parts(context); + } ), - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + _iconButton( + L10().searchParts, + FontAwesomeIcons.search, + callback: () { + _searchParts(); + } + ), + // TODO - Re-add starred parts link + /* + Column( children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.boxes), - tooltip: L10().stock, - onPressed: () { _stock(context); }, - ), - Text(L10().stock), - ], - ), - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.search), - tooltip: L10().searchStock, - onPressed: _searchStock, - ), - Text(L10().searchStock), - ], + IconButton( + icon: FaIcon(FontAwesomeIcons.solidStar), + onPressed: () { + Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); + }, ), + Text("Starred Parts"), ] ), - Spacer(), - // TODO - Re-add these when the features actually do something.. + */ + _iconButton( + L10().stock, + FontAwesomeIcons.boxes, + callback: () { + _stock(context); + } + ), + _iconButton( + L10().searchStock, + FontAwesomeIcons.search, + callback: () { + _searchStock(); + } + ), + _iconButton( + L10().purchaseOrders, + FontAwesomeIcons.shoppingCart, + callback: () { + // TODO + } + ), + _iconButton( + L10().suppliers, + FontAwesomeIcons.building, + callback: () { + _suppliers(); + } + ), + _iconButton( + L10().manufacturers, + FontAwesomeIcons.industry, + callback: () { + // TODO + } + ), /* - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Column( children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.building), - tooltip: "Suppliers", - onPressed: _suppliers, - ), - Text("Suppliers"), - ], + IconButton( + icon: new FaIcon(FontAwesomeIcons.tools), + tooltip: "Build", + onPressed: _unsupported, ), - Column( - children: [ - IconButton( - icon: FaIcon(FontAwesomeIcons.industry), - tooltip: "Manufacturers", - onPressed: _manufacturers, - ), - Text("Manufacturers") - ], - ), - Column( - children: [ - IconButton( - icon: FaIcon(FontAwesomeIcons.userTie), - tooltip: "Customers", - onPressed: _customers, - ), - Text("Customers"), - ] - ) + Text("Build"), ], ), - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, + Column( children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.tools), - tooltip: "Build", - onPressed: _unsupported, - ), - Text("Build"), - ], + IconButton( + icon: new FaIcon(FontAwesomeIcons.shoppingCart), + tooltip: "Order", + onPressed: _unsupported, ), - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.shoppingCart), - tooltip: "Order", - onPressed: _unsupported, - ), - Text("Order"), - ] - ), - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.truck), - tooltip: "Ship", - onPressed: _unsupported, - ), - Text("Ship"), - ] - ) - ], + Text("Order"), + ] ), - Spacer(), - */ - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + Column( children: [ - Expanded( - child: _serverTile(), + IconButton( + icon: new FaIcon(FontAwesomeIcons.truck), + tooltip: "Ship", + onPressed: _unsupported, ), - ], - ), - ]), + Text("Ship"), + ] + ) + ], ), - ), + Spacer(), + */ + /* + Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: _serverTile(), + ), + ], + ), + ]), + */ + ], + ) ); } } From 7d80b4f151d631b0625bd06d26db26dda2e55237 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 22:43:18 +1000 Subject: [PATCH 04/70] Use GridView.extent instead --- lib/l10n | 2 +- lib/widget/home.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n b/lib/l10n index 3c7806d0..bec61c2c 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit 3c7806d03887b8380efa22b8c1ca0e3eca2b98ad +Subproject commit bec61c2c8f076f4dc340962381e5bd6dd460f41b diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 39991a1d..7009393d 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -228,8 +228,8 @@ class _InvenTreeHomePageState extends State { ], ), drawer: new InvenTreeDrawer(context), - body: GridView.count( - crossAxisCount: MediaQuery.of(context).orientation == Orientation.portrait ? 3 : 5, + body: GridView.extent( + maxCrossAxisExtent: 150, shrinkWrap: true, physics: ClampingScrollPhysics(), children: [ From d28392bae3719489ddbf26b24405e1219ebc55cb Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 23:15:33 +1000 Subject: [PATCH 05/70] Simplify grid layout --- lib/widget/home.dart | 172 +++++++++++++++++++++++++++---------------- 1 file changed, 109 insertions(+), 63 deletions(-) diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 7009393d..5056d795 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -64,31 +64,42 @@ class _InvenTreeHomePageState extends State { scanQrCode(context); } - void _parts(BuildContext context) { + void _showParts(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); } - void _stock(BuildContext context) { + void _showStarredParts(BuildContext context) { + if (!InvenTreeAPI().checkConnection(context)) return; + + // TODO + // Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); + } + + void _showStock(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } - void _suppliers() { + void _showPurchaseOrders(BuildContext context) { + if (!InvenTreeAPI().checkConnection(context)) return; + } + + void _showSuppliers(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"}))); } - void _manufacturers() { + void _showManufacturers(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"}))); } - void _customers() { + void _showCustomers(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); @@ -183,6 +194,36 @@ class _InvenTreeHomePageState extends State { } } + Widget _header(String label) { + return Card( + margin: EdgeInsets.symmetric( + vertical: 1, + horizontal: 10, + ), + child: ListTile( + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 1 + ), + title: Text( + label, + style: TextStyle( + fontWeight: FontWeight.bold, + ), + ), + ), + ); + } + + Widget _grid(List children) { + return GridView.extent( + maxCrossAxisExtent: 140, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + children: children, + ); + } + Widget _iconButton(String label, IconData icon, {Function()? callback}) { return GestureDetector( @@ -194,8 +235,16 @@ class _InvenTreeHomePageState extends State { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - FaIcon(icon), - Text(label), + FaIcon( + icon, + color: COLOR_CLICK, + ), + Divider( + height: 10, + ), + Text( + label, + ), ] ) ), @@ -218,21 +267,21 @@ class _InvenTreeHomePageState extends State { appBar: AppBar( title: Text(L10().appTitle), actions: [ - /* - IconButton( - icon: FaIcon(FontAwesomeIcons.search), - tooltip: L10().search, - onPressed: _searchParts, - ), - */ + // IconButton( + // icon: FaIcon(FontAwesomeIcons.barcode), + // tooltip: L10().scanBarcode, + // onPressed: () { + // _scan(context); + // }, + // ), ], ), drawer: new InvenTreeDrawer(context), - body: GridView.extent( - maxCrossAxisExtent: 150, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - children: [ + body: ListView( + physics: ClampingScrollPhysics(), + shrinkWrap: true, + children: [ + _grid([ _iconButton( L10().scanBarcode, FontAwesomeIcons.barcode, @@ -241,69 +290,64 @@ class _InvenTreeHomePageState extends State { } ), _iconButton( - L10().parts, - FontAwesomeIcons.shapes, - callback: () { - _parts(context); - } + L10().search, + FontAwesomeIcons.search, + callback: () { + // TODO: Launch "generic" search widget + } ), _iconButton( - L10().searchParts, - FontAwesomeIcons.search, - callback: () { - _searchParts(); - } + L10().parts, + FontAwesomeIcons.shapes, + callback: () { + _showParts(context); + } ), - // TODO - Re-add starred parts link - /* + + // TODO - Re-add starred parts link + /* Column( children: [ IconButton( icon: FaIcon(FontAwesomeIcons.solidStar), onPressed: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); + }, ), Text("Starred Parts"), ] ), */ + _iconButton( - L10().stock, - FontAwesomeIcons.boxes, - callback: () { - _stock(context); - } + L10().stock, + FontAwesomeIcons.boxes, + callback: () { + _showStock(context); + } ), _iconButton( - L10().searchStock, - FontAwesomeIcons.search, - callback: () { - _searchStock(); - } + L10().purchaseOrders, + FontAwesomeIcons.shoppingCart, + callback: () { + _showPurchaseOrders(context); + } ), _iconButton( - L10().purchaseOrders, - FontAwesomeIcons.shoppingCart, - callback: () { - // TODO - } + L10().suppliers, + FontAwesomeIcons.building, + callback: () { + _showSuppliers(context); + } ), _iconButton( - L10().suppliers, - FontAwesomeIcons.building, - callback: () { - _suppliers(); - } + L10().manufacturers, + FontAwesomeIcons.industry, + callback: () { + _showManufacturers(context); + } ), - _iconButton( - L10().manufacturers, - FontAwesomeIcons.industry, - callback: () { - // TODO - } - ), - /* + /* Spacer(), Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, @@ -342,7 +386,7 @@ class _InvenTreeHomePageState extends State { ), Spacer(), */ - /* + /* Spacer(), Row( mainAxisAlignment: MainAxisAlignment.center, @@ -355,8 +399,10 @@ class _InvenTreeHomePageState extends State { ), ]), */ - ], - ) + ], + ) + ] + ), ); } } From dcfd9d8dd49689111abd5fc19c779ba6cdaed650 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 27 Sep 2021 23:24:06 +1000 Subject: [PATCH 06/70] Add "customers" button to home screen --- lib/widget/home.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 5056d795..39609ccf 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -87,6 +87,7 @@ class _InvenTreeHomePageState extends State { if (!InvenTreeAPI().checkConnection(context)) return; } + void _showSuppliers(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; @@ -346,6 +347,13 @@ class _InvenTreeHomePageState extends State { callback: () { _showManufacturers(context); } + ), + _iconButton( + L10().customers, + FontAwesomeIcons.userTie, + callback: () { + _showCustomers(context); + } ), /* Spacer(), From 06631803c861b64500c5ab5ed5373f3561dd952b Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 00:00:08 +1000 Subject: [PATCH 07/70] Display list of outstanding purchase orders --- lib/inventree/purchase_order.dart | 26 +++- lib/widget/home.dart | 15 +- lib/widget/purchase_order_list.dart | 203 ++++++++++++++++++++++++++++ 3 files changed, 238 insertions(+), 6 deletions(-) create mode 100644 lib/widget/purchase_order_list.dart diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 54a2cb9c..877bbaba 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -1,3 +1,5 @@ +import 'package:inventree/inventree/company.dart'; + import 'model.dart'; // TODO: In the future, status codes should be retrieved from the server @@ -8,7 +10,7 @@ const int PO_STATUS_CANCELLED = 40; const int PO_STATUS_LOST = 50; const int PO_STATUS_RETURNED = 60; -class InvenTreePO extends InvenTreeModel { +class InvenTreePurchaseOrder extends InvenTreeModel { @override String get URL => "order/po/"; @@ -26,7 +28,14 @@ class InvenTreePO extends InvenTreeModel { }; } - InvenTreePO() : super(); + InvenTreePurchaseOrder() : super(); + + @override + Map defaultListFilters() { + return { + "supplier_detail": "true", + }; + } String get issueDate => jsondata['issue_date'] ?? ""; @@ -44,7 +53,14 @@ class InvenTreePO extends InvenTreeModel { int get responsible => jsondata['responsible'] ?? -1; - int get supplier => jsondata['supplier'] ?? -1; + int get supplierId => jsondata['supplier'] ?? -1; + + InvenTreeCompany get supplier { + + dynamic supplier_detail = jsondata["supplier_detail"] ?? {}; + + return InvenTreeCompany.fromJson(supplier_detail); + } String get supplierReference => jsondata['supplier_reference'] ?? ""; @@ -56,11 +72,11 @@ class InvenTreePO extends InvenTreeModel { bool get isFailed => this.status == PO_STATUS_CANCELLED || this.status == PO_STATUS_LOST || this.status == PO_STATUS_RETURNED; - InvenTreePO.fromJson(Map json) : super.fromJson(json); + InvenTreePurchaseOrder.fromJson(Map json) : super.fromJson(json); @override InvenTreeModel createFromJson(Map json) { - return InvenTreePO.fromJson(json); + return InvenTreePurchaseOrder.fromJson(json); } } diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 39609ccf..bb136658 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -15,6 +15,7 @@ import 'package:inventree/settings/login.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/purchase_order_list.dart'; import 'package:inventree/widget/search.dart'; import 'package:inventree/widget/spinner.dart'; import 'package:inventree/widget/drawer.dart'; @@ -85,6 +86,14 @@ class _InvenTreeHomePageState extends State { void _showPurchaseOrders(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget( + ) + ) + ); } @@ -238,7 +247,7 @@ class _InvenTreeHomePageState extends State { children: [ FaIcon( icon, - color: COLOR_CLICK, + color: callback == null ? Colors.grey : COLOR_CLICK, ), Divider( height: 10, @@ -334,6 +343,10 @@ class _InvenTreeHomePageState extends State { _showPurchaseOrders(context); } ), + _iconButton( + L10().salesOrders, + FontAwesomeIcons.truck, + ), _iconButton( L10().suppliers, FontAwesomeIcons.building, diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart new file mode 100644 index 00000000..ed2bf7e8 --- /dev/null +++ b/lib/widget/purchase_order_list.dart @@ -0,0 +1,203 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:inventree/inventree/sentry.dart'; +import 'package:inventree/widget/paginator.dart'; +import 'package:inventree/widget/refreshable_state.dart'; + +import '../l10.dart'; + +import 'package:inventree/api.dart'; +import 'package:inventree/inventree/purchase_order.dart'; + +/* + * Widget class for displaying a list of Purchase Orders + */ +class PurchaseOrderListWidget extends StatefulWidget { + + PurchaseOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); + + final Map filters; + + @override + _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(filters); +} + + +class _PurchaseOrderListWidgetState extends RefreshableState { + + _PurchaseOrderListWidgetState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().purchaseOrders; + + @override + Widget getBody(BuildContext context) { + return _PaginatedPurchaseOrderList(filters); + } +} + + +class _PaginatedPurchaseOrderList extends StatefulWidget { + + _PaginatedPurchaseOrderList(this.filters, {this.onTotalChanged}); + + final Map filters; + + Function(int)? onTotalChanged; + + @override + _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters, onTotalChanged); + +} + + +class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList> { + + _PaginatedPurchaseOrderListState(this.filters, this.onTotalChanged); + + static const _pageSize = 25; + + String _searchTerm = ""; + + Function(int)? onTotalChanged; + + final Map filters; + + final PagingController _pagingController = PagingController(firstPageKey: 0); + + final TextEditingController searchController = TextEditingController(); + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + int resultCount = 0; + + Future _fetchPage(int pageKey) async { + try { + Map params = {}; + + params["search"] = _searchTerm; + + // Only return results for open purchase orders + params["outstanding"] = "true"; + + // Copy across provided filters + for (String key in filters.keys) { + params[key] = filters[key] ?? ''; + } + + final page = await InvenTreePurchaseOrder().listPaginated( + _pageSize, + pageKey, + filters: params + ); + + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + List orders = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreePurchaseOrder) { + orders.add(result); + } else { + print("Result is not valid PurchaseOrder:"); + print(result.jsondata); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(orders); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(orders, nextPageKey); + } + + if (onTotalChanged != null) { + onTotalChanged!(pageCount); + } + + setState(() { + resultCount = pageCount; + }); + } catch (error, stackTrace) { + print("Error! - ${error.toString()}"); + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void updateSearchTerm() { + _searchTerm = searchController.text; + _pagingController.refresh(); + } + + Widget _buildOrder(BuildContext context, InvenTreePurchaseOrder order) { + + var supplier = order.supplier; + + return ListTile( + title: Text(order.reference), + subtitle: Text(order.description), + leading: InvenTreeAPI().getImage( + supplier.thumbnail, + width: 40, + height: 40, + ), + onTap: () async { + // TODO - Display purchase order information + }, + ); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return _buildOrder(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget(L10().companyNoResults); + } + ), + separatorBuilder: (context, index) => const Divider(height: 1), + ) + ], + ) + ) + ], + ); + } +} \ No newline at end of file From 4cae0870320d7c9f9aece14db0f22cd13da07a66 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 00:04:07 +1000 Subject: [PATCH 08/70] Improve rendering of missing image --- lib/api.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/api.dart b/lib/api.dart index a0bf3d1b..baeee1fe 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -5,6 +5,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; +import 'package:inventree/app_colors.dart'; import 'package:open_file/open_file.dart'; import 'package:flutter/cupertino.dart'; @@ -957,7 +958,7 @@ class InvenTreeAPI { return new CachedNetworkImage( imageUrl: url, placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => Icon(FontAwesomeIcons.exclamation), + errorWidget: (context, url, error) => FaIcon(FontAwesomeIcons.timesCircle, color: COLOR_DANGER), httpHeaders: defaultHeaders(), height: height, width: width, From 1cc5d1ffe64fe4714e12f0c6e8448ae5d1ef8269 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 00:17:54 +1000 Subject: [PATCH 09/70] Display number of line items in each order --- lib/widget/purchase_order_list.dart | 1 + pubspec.lock | 16 +++++++++++++++- pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index ed2bf7e8..a0e71009 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -164,6 +164,7 @@ class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList width: 40, height: 40, ), + trailing: Text("${order.lineItems}"), onTap: () async { // TODO - Display purchase order information }, diff --git a/pubspec.lock b/pubspec.lock index 8c201dda..bf07cf78 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -49,7 +49,21 @@ packages: name: cached_network_image url: "https://pub.dartlang.org" source: hosted - version: "3.0.0" + version: "3.1.0" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" camera: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 6e5e8c00..35538e4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: cupertino_icons: ^1.0.3 http: ^0.13.0 - cached_network_image: ^3.0.0 # Download and cache remote images + cached_network_image: ^3.1.0 # Download and cache remote images qr_code_scanner: ^0.5.2 # Barcode scanning package_info_plus: ^1.0.4 # App information introspection device_info_plus: ^2.1.0 # Information about the device From 61a9557df58f99e74265ae434186605c3d020241 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 00:45:48 +1000 Subject: [PATCH 10/70] Display purchase order info --- lib/inventree/purchase_order.dart | 17 +++- lib/widget/company_detail.dart | 1 - lib/widget/purchase_order_detail.dart | 138 ++++++++++++++++++++++++++ lib/widget/purchase_order_list.dart | 13 ++- 4 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 lib/widget/purchase_order_detail.dart diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 877bbaba..71664b43 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -30,6 +30,13 @@ class InvenTreePurchaseOrder extends InvenTreeModel { InvenTreePurchaseOrder() : super(); + @override + Map defaultGetFilters() { + return { + "supplier_detail": "true", + }; + } + @override Map defaultListFilters() { return { @@ -55,11 +62,15 @@ class InvenTreePurchaseOrder extends InvenTreeModel { int get supplierId => jsondata['supplier'] ?? -1; - InvenTreeCompany get supplier { + InvenTreeCompany? get supplier { - dynamic supplier_detail = jsondata["supplier_detail"] ?? {}; + dynamic supplier_detail = jsondata["supplier_detail"] ?? null; - return InvenTreeCompany.fromJson(supplier_detail); + if (supplier_detail == null) { + return null; + } else { + return InvenTreeCompany.fromJson(supplier_detail); + } } String get supplierReference => jsondata['supplier_reference'] ?? ""; diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index bc87322e..86d4c5e6 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -1,6 +1,5 @@ import 'package:inventree/api.dart'; -import 'package:inventree/api_form.dart'; import 'package:inventree/app_colors.dart'; import 'package:inventree/inventree/company.dart'; import 'package:inventree/widget/refreshable_state.dart'; diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart new file mode 100644 index 00000000..f368b082 --- /dev/null +++ b/lib/widget/purchase_order_detail.dart @@ -0,0 +1,138 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:inventree/api.dart'; +import 'package:inventree/inventree/company.dart'; +import 'package:inventree/inventree/purchase_order.dart'; +import 'package:inventree/widget/refreshable_state.dart'; + +import '../l10.dart'; +import 'location_display.dart'; + + +class PurchaseOrderDetailWidget extends StatefulWidget { + + PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); + + final InvenTreePurchaseOrder order; + + @override + _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState(order); +} + + +class _PurchaseOrderDetailState extends RefreshableState { + + _PurchaseOrderDetailState(this.order); + + final InvenTreePurchaseOrder order; + + @override + String getAppBarTitle(BuildContext context) => L10().purchaseOrder; + + @override + List getAppBarActions(BuildContext context) { + List actions = []; + + if (InvenTreeAPI().checkPermission("purchase_order", "change")) { + actions.add( + IconButton( + icon: FaIcon(FontAwesomeIcons.edit), + tooltip: L10().edit, + onPressed: () { + editOrder(context); + } + ) + ); + } + + return actions; + } + + @override + Future request() async { + await order.reload(); + } + + void editOrder(BuildContext context) async { + + order.editForm( + context, + L10().purchaseOrderEdit, + onSuccess: (data) async { + refresh(); + } + ); + } + + List orderTiles(BuildContext context) { + + List tiles = []; + + InvenTreeCompany? supplier = order.supplier; + + print(order.jsondata); + + tiles.add(Card( + child: ListTile( + title: Text(order.reference), + subtitle: Text(order.description), + leading: supplier == null ? null : InvenTreeAPI().getImage(supplier.thumbnail, width: 40, height: 40), + trailing: Text("${order.lineItems}"), + ) + )); + + return tiles; + + } + + @override + Widget getBody(BuildContext context) { + + return Center( + child: getSelectedWidget(context, tabIndex), + ); + } + + Widget getSelectedWidget(BuildContext context, int index) { + switch (index) { + case 0: + return ListView( + children: orderTiles(context) + ); + case 2: + // Stock items received against this order + Map filters = { + "purchase_order": "${order.pk}" + }; + + return PaginatedStockList(filters); + + default: + return ListView(); + } + } + + @override + Widget getBottomNavBar(BuildContext context) { + return BottomNavigationBar( + currentIndex: tabIndex, + onTap: onTabSelectionChanged, + items: [ + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.info), + label: L10().details + ), + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.thList), + label: L10().lineItems, + ), + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.boxes), + label: L10().stockItems + ) + ], + ); + } + +} \ No newline at end of file diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index a0e71009..86f3e61a 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -1,8 +1,10 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:inventree/inventree/company.dart'; import 'package:inventree/inventree/sentry.dart'; import 'package:inventree/widget/paginator.dart'; +import 'package:inventree/widget/purchase_order_detail.dart'; import 'package:inventree/widget/refreshable_state.dart'; import '../l10.dart'; @@ -154,19 +156,24 @@ class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList Widget _buildOrder(BuildContext context, InvenTreePurchaseOrder order) { - var supplier = order.supplier; + InvenTreeCompany? supplier = order.supplier; return ListTile( title: Text(order.reference), subtitle: Text(order.description), - leading: InvenTreeAPI().getImage( + leading: supplier == null ? null : InvenTreeAPI().getImage( supplier.thumbnail, width: 40, height: 40, ), trailing: Text("${order.lineItems}"), onTap: () async { - // TODO - Display purchase order information + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderDetailWidget(order) + ) + ); }, ); } From 68d542cb6bb6bb0e106e71d5402b4abf88894246 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 01:14:58 +1000 Subject: [PATCH 11/70] Add API form support for date fields --- lib/api_form.dart | 34 ++++++++++++++++++++++++++++++++++ lib/inventree/stock.dart | 2 ++ pubspec.lock | 7 +++++++ pubspec.yaml | 1 + 4 files changed, 44 insertions(+) diff --git a/lib/api_form.dart b/lib/api_form.dart index 88ffa885..4b496fe9 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:dropdown_search/dropdown_search.dart'; +import 'package:date_field/date_field.dart'; import 'package:inventree/api.dart'; import 'package:inventree/app_colors.dart'; @@ -167,6 +168,8 @@ class APIFormField { case "file upload": case "image upload": return _constructFileField(); + case "date": + return _constructDateField(); default: return ListTile( title: Text( @@ -179,6 +182,30 @@ class APIFormField { } } + // Field for displaying and selecting dates + Widget _constructDateField() { + + return DateTimeFormField( + mode: DateTimeFieldPickerMode.date, + decoration: InputDecoration( + helperText: helpText, + helperStyle: _helperStyle(), + labelText: label, + labelStyle: _labelStyle(), + ), + initialValue: DateTime.tryParse(value ?? ""), + autovalidateMode: AutovalidateMode.always, + validator: (e) { + // TODO + }, + onDateSelected: (DateTime dt) { + data['value'] = dt.toString().split(" ").first; + }, + ); + + } + + // Field for selecting and uploading files Widget _constructFileField() { @@ -418,6 +445,13 @@ class APIFormField { style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), ) : null, ); + case "owner": + String name = item["name"] ?? ""; + bool isGroup = (item["label"] ?? "") == "group"; + return ListTile( + title: Text(name), + leading: FaIcon(isGroup ? FontAwesomeIcons.users : FontAwesomeIcons.user), + ); default: return ListTile( title: Text( diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 795dbda7..ad743f8f 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -223,6 +223,8 @@ class InvenTreeStockItem extends InvenTreeModel { return pp.isNotEmpty && pp.trim() != "-"; } + int get purchaseOrderId => jsondata['purchase_order'] ?? -1; + int get trackingItemCount => (jsondata['tracking_items'] ?? 0) as int; // Date of last update diff --git a/pubspec.lock b/pubspec.lock index bf07cf78..6a5f06d6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" + date_field: + dependency: "direct main" + description: + name: date_field + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.2" device_info_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 35538e4c..f562a966 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: sentry_flutter: 5.0.0 # Error reporting image_picker: ^0.8.3 # Select or take photos file_picker: ^4.0.0 # Select files from the device + date_field: ^2.1.2 # Date / time picker url_launcher: 6.0.9 # Open link in system browser open_file: 3.2.1 # Open local files flutter_markdown: ^0.6.2 # Rendering markdown From a0a8f56490c35bb6e57cb5a3e2018e69c6d91aea Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 01:15:30 +1000 Subject: [PATCH 12/70] Render more information on PurchaseOrder detail page --- lib/inventree/purchase_order.dart | 1 - lib/l10n | 2 +- lib/widget/purchase_order_detail.dart | 38 ++++++++++++++++++++++++--- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 71664b43..f9946815 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -19,7 +19,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel { Map formFields() { return { "reference": {}, - "supplier": {}, "supplier_reference": {}, "description": {}, "target_date": {}, diff --git a/lib/l10n b/lib/l10n index bec61c2c..ecd831b2 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit bec61c2c8f076f4dc340962381e5bd6dd460f41b +Subproject commit ecd831b26c3739c235db65867d39ff9af517b09a diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index f368b082..ff16680f 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -71,17 +71,49 @@ class _PurchaseOrderDetailState extends RefreshableState Date: Tue, 28 Sep 2021 08:11:10 +1000 Subject: [PATCH 13/70] Retrieve list of line items from server --- lib/inventree/purchase_order.dart | 48 +++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index f9946815..72ac7cdf 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -1,4 +1,5 @@ import 'package:inventree/inventree/company.dart'; +import 'package:inventree/inventree/part.dart'; import 'model.dart'; @@ -82,6 +83,25 @@ class InvenTreePurchaseOrder extends InvenTreeModel { bool get isFailed => this.status == PO_STATUS_CANCELLED || this.status == PO_STATUS_LOST || this.status == PO_STATUS_RETURNED; + Future> getLineItems() async { + + final results = await InvenTreePOLineItem().list( + filters: { + "order": "${pk}", + } + ); + + List items = []; + + for (var result in results) { + if (result is InvenTreePOLineItem) { + items.add(result); + } + } + + return items; + } + InvenTreePurchaseOrder.fromJson(Map json) : super.fromJson(json); @override @@ -110,15 +130,39 @@ class InvenTreePOLineItem extends InvenTreeModel { }; } + @override + Map defaultGetFilters() { + return { + "part_detail": "true", + }; + } + + @override + Map defaultListFilters() { + return { + "part_detail": "true", + }; + } + double get quantity => jsondata['quantity'] ?? 0; double get received => jsondata['received'] ?? 0; String get reference => jsondata['reference'] ?? ""; - int get order => jsondata['order'] ?? -1; + int get orderId => jsondata['order'] ?? -1; - int get part => jsondata['part'] ?? -1; + int get supplirtPartId => jsondata['part'] ?? -1; + + InvenTreePart? get part { + dynamic part_detail = jsondata["part_detail"] ?? null; + + if (part_detail == null) { + return null; + } else { + return InvenTreePart.fromJson(part_detail); + } + } double get purchasePrice => double.parse(jsondata['purchase_price']); From c00f5d326613dfe4539534ea38bd328ebbb8a9b2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 08:11:22 +1000 Subject: [PATCH 14/70] Refresh line items --- lib/widget/purchase_order_detail.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index ff16680f..18a29234 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -2,8 +2,11 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:inventree/api.dart'; +import 'package:inventree/app_colors.dart'; import 'package:inventree/inventree/company.dart'; +import 'package:inventree/inventree/part.dart'; import 'package:inventree/inventree/purchase_order.dart'; +import 'package:inventree/widget/company_detail.dart'; import 'package:inventree/widget/refreshable_state.dart'; import '../l10.dart'; @@ -27,6 +30,8 @@ class _PurchaseOrderDetailState extends RefreshableState lines = []; + @override String getAppBarTitle(BuildContext context) => L10().purchaseOrder; @@ -52,6 +57,9 @@ class _PurchaseOrderDetailState extends RefreshableState request() async { await order.reload(); + + lines = await order.getLineItems(); + } void editOrder(BuildContext context) async { From e538174b0721b995c347c7be8ca2b859f5a35fd1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 11:40:15 +1000 Subject: [PATCH 15/70] Bug fix for stock item actions - initial quantity --- lib/widget/stock_detail.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 28ac29cb..862d9628 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -225,7 +225,7 @@ class _StockItemDisplayState extends RefreshableState { void _countStockDialog() async { - _quantityController.text = item.quantityString; + _quantityController.text = item.quantity.toString(); _notesController.clear(); showFormDialog(L10().countStock, @@ -292,7 +292,7 @@ class _StockItemDisplayState extends RefreshableState { int? location_pk; - _quantityController.text = "${item.quantityString}"; + _quantityController.text = "${item.quantity}"; showFormDialog(L10().transferStock, key: _moveStockKey, From cca56299f80a37d6d8ea9cd9268ffe804d98c755 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 11:42:34 +1000 Subject: [PATCH 16/70] Release notes update --- assets/release_notes.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/assets/release_notes.md b/assets/release_notes.md index 51ecff91..d6cd353f 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,6 +1,15 @@ ## InvenTree App Release Notes --- +### 0.5.0 - October 2021 +--- + +- Display Purchase Order details +- Edit Purchase Order information +- Display Company details (supplier / manufacturer / customer) +- Edit Company information +- Fixed bug relating to stock transfer for parts with specified "units" + ### 0.4.7 - September 2021 --- From 54d8c1759c0b5ebab4e3e8eebc11b2c26a4ab1ae Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 11:43:56 +1000 Subject: [PATCH 17/70] Display line items in purchase order view --- lib/inventree/company.dart | 4 ++ lib/inventree/purchase_order.dart | 17 ++++- lib/l10n | 2 +- lib/widget/purchase_order_detail.dart | 96 ++++++++++++++++++++++++--- lib/widget/purchase_order_list.dart | 2 +- 5 files changed, 109 insertions(+), 12 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index aea0325c..920aaa65 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -103,6 +103,10 @@ class InvenTreeSupplierPart extends InvenTreeModel { int get partId => jsondata['part'] ?? -1; + String get partImage => jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb; + + String get partName => jsondata["part_detail"]["full_name"] ?? ""; + @override InvenTreeModel createFromJson(Map json) { var part = InvenTreeSupplierPart.fromJson(json); diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 72ac7cdf..3b279485 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -52,7 +52,7 @@ class InvenTreePurchaseOrder extends InvenTreeModel { String get targetDate => jsondata['target_date'] ?? ""; - int get lineItems => jsondata['line_items'] ?? 0; + int get lineItemCount => jsondata['line_items'] ?? 0; bool get overdue => jsondata['overdue'] ?? false; @@ -144,10 +144,14 @@ class InvenTreePOLineItem extends InvenTreeModel { }; } + bool get isComplete => received >= quantity; + double get quantity => jsondata['quantity'] ?? 0; double get received => jsondata['received'] ?? 0; + double get outstanding => quantity - received; + String get reference => jsondata['reference'] ?? ""; int get orderId => jsondata['order'] ?? -1; @@ -164,6 +168,17 @@ class InvenTreePOLineItem extends InvenTreeModel { } } + InvenTreeSupplierPart? get supplierPart { + + dynamic detail = jsondata["supplier_part_detail"] ?? null; + + if (detail == null) { + return null; + } else { + return InvenTreeSupplierPart.fromJson(detail); + } + } + double get purchasePrice => double.parse(jsondata['purchase_price']); String get purchasePriceCurrency => jsondata['purchase_price_currency'] ?? ""; diff --git a/lib/l10n b/lib/l10n index ecd831b2..dba9551b 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit ecd831b26c3739c235db65867d39ff9af517b09a +Subproject commit dba9551b897dca766088e5470704e20edb770499 diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 18a29234..8b84e9cb 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -32,6 +32,8 @@ class _PurchaseOrderDetailState extends RefreshableState lines = []; + int completedLines = 0; + @override String getAppBarTitle(BuildContext context) => L10().purchaseOrder; @@ -60,6 +62,14 @@ class _PurchaseOrderDetailState extends RefreshableState orderTiles(BuildContext context) { List tiles = []; InvenTreeCompany? supplier = order.supplier; - tiles.add(Card( - child: ListTile( - title: Text(order.reference), - subtitle: Text(order.description), - leading: supplier == null ? null : InvenTreeAPI().getImage(supplier.thumbnail, width: 40, height: 40), - ) - )); + tiles.add(headerTile(context)); if (supplier != null) { tiles.add(ListTile( title: Text(L10().supplier), subtitle: Text(supplier.name), - leading: FaIcon(FontAwesomeIcons.building), + leading: FaIcon(FontAwesomeIcons.building, color: COLOR_CLICK), onTap: () { - // TODO - Navigate to "supplier" page + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CompanyDetailWidget(supplier) + ) + ); }, )); } @@ -106,6 +129,30 @@ class _PurchaseOrderDetailState extends RefreshableState lineTiles(BuildContext context) { + + List tiles = []; + + tiles.add(headerTile(context)); + + for (var line in lines) { + + InvenTreeSupplierPart? supplierPart = line.supplierPart; + + if (supplierPart == null) { + continue; + } else { + tiles.add( + ListTile( + title: Text(supplierPart.SKU), + subtitle: Text(supplierPart.partName), + leading: InvenTreeAPI().getImage(supplierPart.partImage, width: 40, height: 40), + trailing: Text("${line.quantity}"), + ) + ); + } + } + + return tiles; + } + @override Widget getBody(BuildContext context) { @@ -140,6 +214,10 @@ class _PurchaseOrderDetailState extends RefreshableState filters = { diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 86f3e61a..66bf0a1d 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -166,7 +166,7 @@ class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList width: 40, height: 40, ), - trailing: Text("${order.lineItems}"), + trailing: Text("${order.lineItemCount}"), onTap: () async { Navigator.push( context, From c1a1ef0ad2396dc80a8e3cb226b000e54331d014 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 12:05:44 +1000 Subject: [PATCH 18/70] Cleanup home page widget --- lib/api.dart | 2 +- lib/l10n | 2 +- lib/widget/home.dart | 144 +++++++++++++++++++++++-------------------- 3 files changed, 78 insertions(+), 70 deletions(-) diff --git a/lib/api.dart b/lib/api.dart index baeee1fe..56709d4c 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -240,7 +240,7 @@ class InvenTreeAPI { if (address.isEmpty || username.isEmpty || password.isEmpty) { showSnackIcon( - "Incomplete profile details", + L10().incompleteDetails, icon: FontAwesomeIcons.exclamationCircle, success: false ); diff --git a/lib/l10n b/lib/l10n index dba9551b..ed69d6ef 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit dba9551b897dca766088e5470704e20edb770499 +Subproject commit ed69d6efe7672770ee27f4d1b4fc8ab7793266da diff --git a/lib/widget/home.dart b/lib/widget/home.dart index bb136658..1d7ef528 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -17,6 +17,7 @@ import 'package:inventree/widget/company_list.dart'; import 'package:inventree/widget/location_display.dart'; import 'package:inventree/widget/purchase_order_list.dart'; import 'package:inventree/widget/search.dart'; +import 'package:inventree/widget/snacks.dart'; import 'package:inventree/widget/spinner.dart'; import 'package:inventree/widget/drawer.dart'; @@ -204,37 +205,15 @@ class _InvenTreeHomePageState extends State { } } - Widget _header(String label) { - return Card( - margin: EdgeInsets.symmetric( - vertical: 1, - horizontal: 10, - ), - child: ListTile( - contentPadding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 1 - ), - title: Text( - label, - style: TextStyle( - fontWeight: FontWeight.bold, - ), - ), - ), - ); - } + Widget _iconButton(BuildContext context, String label, IconData icon, {Function()? callback, String role = "", String permission = ""}) { - Widget _grid(List children) { - return GridView.extent( - maxCrossAxisExtent: 140, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - children: children, - ); - } + bool connected = InvenTreeAPI().isConnected(); - Widget _iconButton(String label, IconData icon, {Function()? callback}) { + bool allowed = true; + + if (role.isNotEmpty || permission.isNotEmpty) { + allowed = InvenTreeAPI().checkPermission(role, permission); + } return GestureDetector( child: Card( @@ -247,7 +226,7 @@ class _InvenTreeHomePageState extends State { children: [ FaIcon( icon, - color: callback == null ? Colors.grey : COLOR_CLICK, + color: connected && allowed ? COLOR_CLICK : Colors.grey, ), Divider( height: 10, @@ -258,11 +237,27 @@ class _InvenTreeHomePageState extends State { ] ) ), - onTap: callback, - ); + onTap: () { + if (!allowed) { + showSnackIcon( + L10().permissionRequired, + icon: FontAwesomeIcons.exclamationCircle, + success: false, + ); + + return; + } + + if (callback != null) { + callback(); + } + + }, + ); } + @override Widget build(BuildContext context) { @@ -291,27 +286,34 @@ class _InvenTreeHomePageState extends State { physics: ClampingScrollPhysics(), shrinkWrap: true, children: [ - _grid([ + GridView.extent( + maxCrossAxisExtent: 140, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + children: [ _iconButton( - L10().scanBarcode, - FontAwesomeIcons.barcode, - callback: () { - _scan(context); - } + context, + L10().scanBarcode, + FontAwesomeIcons.barcode, + callback: () { + _scan(context); + } ), _iconButton( - L10().search, - FontAwesomeIcons.search, - callback: () { - // TODO: Launch "generic" search widget - } + context, + L10().search, + FontAwesomeIcons.search, + callback: () { + // TODO: Launch "generic" search widget + } ), _iconButton( - L10().parts, - FontAwesomeIcons.shapes, - callback: () { - _showParts(context); - } + context, + L10().parts, + FontAwesomeIcons.shapes, + callback: () { + _showParts(context); + } ), // TODO - Re-add starred parts link @@ -330,38 +332,44 @@ class _InvenTreeHomePageState extends State { */ _iconButton( - L10().stock, - FontAwesomeIcons.boxes, - callback: () { - _showStock(context); - } + context, + L10().stock, + FontAwesomeIcons.boxes, + callback: () { + _showStock(context); + } ), _iconButton( - L10().purchaseOrders, - FontAwesomeIcons.shoppingCart, - callback: () { - _showPurchaseOrders(context); - } + context, + L10().purchaseOrders, + FontAwesomeIcons.shoppingCart, + callback: () { + _showPurchaseOrders(context); + } ), _iconButton( + context, L10().salesOrders, FontAwesomeIcons.truck, ), _iconButton( - L10().suppliers, - FontAwesomeIcons.building, - callback: () { - _showSuppliers(context); - } + context, + L10().suppliers, + FontAwesomeIcons.building, + callback: () { + _showSuppliers(context); + } ), _iconButton( - L10().manufacturers, - FontAwesomeIcons.industry, - callback: () { - _showManufacturers(context); - } + context, + L10().manufacturers, + FontAwesomeIcons.industry, + callback: () { + _showManufacturers(context); + } ), _iconButton( + context, L10().customers, FontAwesomeIcons.userTie, callback: () { From 037564c6a3e976a05e1cb25ef22337d5823f8da8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 12:19:19 +1000 Subject: [PATCH 19/70] Adds workflow for code linting --- .github/workflows/lint.yaml | 28 ++++++++++++++++++++++++++++ analysis_options.yaml | 8 ++++++++ lib/widget/home.dart | 15 --------------- pubspec.lock | 7 +++++++ pubspec.yaml | 1 + 5 files changed, 44 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/lint.yaml create mode 100644 analysis_options.yaml diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml new file mode 100644 index 00000000..b5300e5d --- /dev/null +++ b/.github/workflows/lint.yaml @@ -0,0 +1,28 @@ +# Run flutter linting checks + +name: linting + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '12.x' + - uses: subosito/flutter-action@v1 + with: + flutter-version: '2.2.3' + - run: flutter pub get + - run: flutter analyze + \ No newline at end of file diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..f177c41e --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:lint/analysis_options.yaml + +linter: + rules: + # ------ Disable individual rules ----- # + # --- # + # Turn off what you don't like. # + # ------------------------------------- # diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 1d7ef528..77f59ef5 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -261,25 +261,10 @@ class _InvenTreeHomePageState extends State { @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( key: _homeKey, appBar: AppBar( title: Text(L10().appTitle), - actions: [ - // IconButton( - // icon: FaIcon(FontAwesomeIcons.barcode), - // tooltip: L10().scanBarcode, - // onPressed: () { - // _scan(context); - // }, - // ), - ], ), drawer: new InvenTreeDrawer(context), body: ListView( diff --git a/pubspec.lock b/pubspec.lock index 6a5f06d6..9d6d2aca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -336,6 +336,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + lint: + dependency: "direct dev" + description: + name: lint + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.0" markdown: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f562a966..230d6628 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -48,6 +48,7 @@ dev_dependencies: flutter_test: sdk: flutter flutter_launcher_icons: + lint: ^1.0.0 flutter_icons: android: true From 5f393bf43e23cc9c335066bc5c69b7a378eea7c3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 12:28:04 +1000 Subject: [PATCH 20/70] Fix workflow file - Checkout submodules --- .github/workflows/lint.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index b5300e5d..1a0a3f6f 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -16,11 +16,16 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 + - name: Checkout code + uses: actions/checkout@v2 + - name: Checkout submodules + uses: textbook/git-checkout-submodule-action@master + - name: Setup Java + uses: actions/setup-java@v1 with: java-version: '12.x' - - uses: subosito/flutter-action@v1 + - name: Setup Flutter + uses: subosito/flutter-action@v1 with: flutter-version: '2.2.3' - run: flutter pub get From 9d95cae612ded3bf7adaa68d8d0ad5b7f40c9158 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 14:29:07 +1000 Subject: [PATCH 21/70] Update CI workflow --- .github/workflows/lint.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1a0a3f6f..2233262c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -18,8 +18,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v2 - - name: Checkout submodules - uses: textbook/git-checkout-submodule-action@master + with: + submodules: recursive - name: Setup Java uses: actions/setup-java@v1 with: From c1152ee2868db506fc9cd486abb0e0ae630b75bd Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 17:53:14 +1000 Subject: [PATCH 22/70] Fixes for type casting --- analysis_options.yaml | 15 +++++++ lib/api.dart | 51 +++++++++++++++++----- lib/api_form.dart | 63 ++++++++++++++-------------- lib/app_settings.dart | 11 +++++ lib/barcode.dart | 64 ++++++++++++++-------------- lib/inventree/company.dart | 32 +++++++------- lib/inventree/model.dart | 50 +++++++++++----------- lib/inventree/part.dart | 32 +++++++------- lib/inventree/purchase_order.dart | 52 +++++++++++------------ lib/inventree/stock.dart | 70 +++++++++++++++---------------- lib/preferences.dart | 2 +- lib/settings/app_settings.dart | 8 ++-- lib/settings/login.dart | 11 ----- lib/user_profile.dart | 26 ++++++------ lib/widget/category_display.dart | 13 ++++-- lib/widget/location_display.dart | 13 ++++-- lib/widget/part_detail.dart | 5 ++- lib/widget/stock_detail.dart | 4 +- 18 files changed, 294 insertions(+), 228 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index f177c41e..2ea080d3 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,8 +1,23 @@ include: package:lint/analysis_options.yaml +analyzer: + exclude: [build/**] + language: + strict-raw-types: true + strong-mode: + implicit-casts: false + linter: rules: # ------ Disable individual rules ----- # # --- # # Turn off what you don't like. # # ------------------------------------- # + + # Make constructors the first thing in every class + sort_constructors_first: true + + prefer_single_quotes: true + + # Blindly follow the Flutter code style, which prefers types everywhere + always_specify_types: true diff --git a/lib/api.dart b/lib/api.dart index 56709d4c..acd02f51 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -51,6 +51,31 @@ class APIResponse { bool clientError() => (statusCode >= 400) && (statusCode < 500); bool serverError() => (statusCode >= 500); + + bool isMap() { + return data != null && data is Map; + } + + Map asMap() { + if (isMap()) { + return data as Map; + } else { + // Empty map + return {}; + } + } + + bool isList() { + return data != null && data is List; + } + + List asList() { + if (isList()) { + return data as List; + } else { + return []; + } + } } @@ -268,8 +293,10 @@ class InvenTreeAPI { return false; } + var data = response.asMap(); + // We expect certain response from the server - if (response.data == null || !response.data.containsKey("server") || !response.data.containsKey("version") || !response.data.containsKey("instance")) { + if (!data.containsKey("server") || !data.containsKey("version") || !data.containsKey("instance")) { showServerError( L10().missingData, @@ -280,11 +307,11 @@ class InvenTreeAPI { } // Record server information - _version = response.data["version"]; - instance = response.data['instance'] ?? ''; + _version = (data["version"] ?? '') as String; + instance = (data['instance'] ?? '') as String; // Default API version is 1 if not provided - _apiVersion = (response.data['apiVersion'] ?? 1) as int; + _apiVersion = (data['apiVersion'] ?? 1) as int; if (_apiVersion < _minApiVersion) { @@ -333,7 +360,9 @@ class InvenTreeAPI { return false; } - if (response.data == null || !response.data.containsKey("token")) { + data = response.asMap(); + + if (!data.containsKey("token")) { showServerError( L10().tokenMissing, L10().tokenMissingFromResponse, @@ -343,7 +372,7 @@ class InvenTreeAPI { } // Return the received token - _token = response.data["token"]; + _token = (data["token"] ?? "") as String; print("Received token - $_token"); // Request user role information @@ -415,9 +444,11 @@ class InvenTreeAPI { return; } - if (response.data.containsKey('roles')) { + var data = response.asMap(); + + if (data.containsKey('roles')) { // Save a local copy of the user roles - roles = response.data['roles']; + roles = response.data['roles'] as Map; } } @@ -438,7 +469,7 @@ class InvenTreeAPI { } try { - List perms = List.from(roles[role]); + List perms = List.from(roles[role] as List); return perms.contains(permission); } catch (error, stackTrace) { sentryReportError(error, stackTrace); @@ -551,7 +582,7 @@ class InvenTreeAPI { showServerError(L10().connectionRefused, error.toString()); } on TimeoutException { showTimeoutError(); - } catch (error, stackTrace) { + } catch (error) { print("Error downloading image:"); print(error.toString()); showServerError(L10().downloadError, error.toString()); diff --git a/lib/api_form.dart b/lib/api_form.dart index 4b496fe9..1b8b472b 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -36,15 +36,15 @@ class APIFormField { final String name; // JSON data which defines the field - final dynamic data; + final Map data; dynamic initial_data; // Get the "api_url" associated with a related field - String get api_url => data["api_url"] ?? ""; + String get api_url => (data["api_url"] ?? '') as String; // Get the "model" associated with a related field - String get model => data["model"] ?? ""; + String get model => (data["model"] ?? '') as String; // Is this field hidden? bool get hidden => (data['hidden'] ?? false) as bool; @@ -71,7 +71,7 @@ class APIFormField { if (f is Map) { f.forEach((key, value) { - _filters[key] = value.toString(); + _filters[key as String] = value.toString(); }); } } @@ -83,7 +83,7 @@ class APIFormField { if (f is Map) { f.forEach((key, value) { - _filters[key] = value.toString(); + _filters[key as String] = value.toString(); }); } } @@ -96,7 +96,7 @@ class APIFormField { // Return the error message associated with this field List errorMessages() { - List errors = data['errors'] ?? []; + List errors = (data['errors'] ?? []) as List; List messages = []; @@ -118,7 +118,7 @@ class APIFormField { String get placeholderText => (data['placeholder'] ?? '').toString(); - List get choices => data["choices"] ?? []; + List get choices => (data["choices"] ?? []) as List; Future loadInitialData() async { @@ -193,7 +193,7 @@ class APIFormField { labelText: label, labelStyle: _labelStyle(), ), - initialValue: DateTime.tryParse(value ?? ""), + initialValue: DateTime.tryParse((value ?? '') as String), autovalidateMode: AutovalidateMode.always, validator: (e) { // TODO @@ -267,7 +267,7 @@ class APIFormField { autoFocusSearchBox: true, showClearButton: !required, itemAsString: (dynamic item) { - return item['display_name']; + return (item['display_name'] ?? '') as String; }, onSaved: (item) { if (item == null) { @@ -349,13 +349,16 @@ class APIFormField { onChanged: null, showClearButton: !required, itemAsString: (dynamic item) { + + Map data = item as Map; + switch (model) { case "part": - return InvenTreePart.fromJson(item).fullname; + return InvenTreePart.fromJson(data).fullname; case "partcategory": - return InvenTreePartCategory.fromJson(item).pathstring; + return InvenTreePartCategory.fromJson(data).pathstring; case "stocklocation": - return InvenTreeStockLocation.fromJson(item).pathstring; + return InvenTreeStockLocation.fromJson(data).pathstring; default: return "itemAsString not implemented for '${model}'"; } @@ -391,19 +394,13 @@ class APIFormField { Widget _renderRelatedField(dynamic item, bool selected, bool extended) { // Render a "related field" based on the "model" type - if (item == null) { - return Text( - helpText, - style: TextStyle( - fontStyle: FontStyle.italic - ), - ); - } + // Convert to JSON + Map data = item as Map; switch (model) { case "part": - var part = InvenTreePart.fromJson(item); + var part = InvenTreePart.fromJson(data); return ListTile( title: Text( @@ -419,7 +416,7 @@ class APIFormField { case "partcategory": - var cat = InvenTreePartCategory.fromJson(item); + var cat = InvenTreePartCategory.fromJson(data); return ListTile( title: Text( @@ -433,7 +430,7 @@ class APIFormField { ); case "stocklocation": - var loc = InvenTreeStockLocation.fromJson(item); + var loc = InvenTreeStockLocation.fromJson(data); return ListTile( title: Text( @@ -446,7 +443,7 @@ class APIFormField { ) : null, ); case "owner": - String name = item["name"] ?? ""; + String name = (item["name"] ?? '') as String; bool isGroup = (item["label"] ?? "") == "group"; return ListTile( title: Text(name), @@ -481,7 +478,7 @@ class APIFormField { readOnly: readOnly, maxLines: multiline ? null : 1, expands: false, - initialValue: value ?? '', + initialValue: (value ?? '') as String, onSaved: (val) { data["value"] = val; }, @@ -501,7 +498,7 @@ class APIFormField { labelStyle: _labelStyle(), helperText: helpText, helperStyle: _helperStyle(), - initial: value, + initial: value as bool, onSaved: (val) { data['value'] = val; }, @@ -537,13 +534,17 @@ Map extractFields(APIResponse response) { return {}; } - if (!response.data.containsKey("actions")) { + var data = response.asMap(); + + if (!data.containsKey("actions")) { return {}; } - var actions = response.data["actions"]; + var actions = response.data["actions"] as Map; - return actions["POST"] ?? actions["PUT"] ?? actions["PATCH"] ?? {}; + dynamic result = actions["POST"] ?? actions["PUT"] ?? actions["PATCH"] ?? {}; + + return result as Map; } /* @@ -599,8 +600,8 @@ Future launchApiForm(BuildContext context, String title, String url, Map remoteField = (availableFields[fieldName] ?? {}) as Map; + Map localField = (fields[fieldName] ?? {}) as Map; // Override defined field parameters, if provided for (String key in localField.keys) { diff --git a/lib/app_settings.dart b/lib/app_settings.dart index 7146b91b..5946249a 100644 --- a/lib/app_settings.dart +++ b/lib/app_settings.dart @@ -22,6 +22,17 @@ class InvenTreeSettingsManager { return value; } + // Load a boolean setting + Future getBool(String key, bool backup) async { + final dynamic value = await getValue(key, backup); + + if (value is bool) { + return value; + } else { + return backup; + } + } + Future setValue(String key, dynamic value) async { await store.record(key).put(await _db, value); diff --git a/lib/barcode.dart b/lib/barcode.dart index de3af32b..60be2aaa 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -101,8 +101,10 @@ class BarcodeHandler { _controller?.resumeCamera(); + Map data = response.asMap(); + // Handle strange response from the server - if (!response.isValid() || response.data == null || !(response.data is Map)) { + if (!response.isValid() || !response.isMap()) { onBarcodeUnknown(context, {}); // We want to know about this one! @@ -118,12 +120,12 @@ class BarcodeHandler { "errorDetail": response.errorDetail, } ); - } else if (response.data.containsKey('error')) { - onBarcodeUnknown(context, response.data); - } else if (response.data.containsKey('success')) { - onBarcodeMatched(context, response.data); + } else if (data.containsKey('error')) { + onBarcodeUnknown(context, data); + } else if (data.containsKey('success')) { + onBarcodeMatched(context, data); } else { - onBarcodeUnhandled(context, response.data); + onBarcodeUnhandled(context, data); } } } @@ -294,35 +296,35 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { L10().barcodeMissingHash, ); } else { + String hash = (data['hash'] ?? '') as String; - // Send the 'hash' code as the UID for the stock item - item.update( - values: { - "uid": data['hash'], - } - ).then((result) { - if (result) { + if (hash.isNotEmpty) { + item.update( + values: { + "uid": hash, + } + ).then((result) { + if (result) { + failureTone(); - failureTone(); + Navigator.of(context).pop(); - Navigator.of(context).pop(); + showSnackIcon( + L10().barcodeAssigned, + success: true, + icon: FontAwesomeIcons.qrcode + ); + } else { + successTone(); - showSnackIcon( - L10().barcodeAssigned, - success: true, - icon: FontAwesomeIcons.qrcode - ); - } else { - - successTone(); - - showSnackIcon( - L10().barcodeNotAssigned, - success: false, - icon: FontAwesomeIcons.qrcode - ); - } - }); + showSnackIcon( + L10().barcodeNotAssigned, + success: false, + icon: FontAwesomeIcons.qrcode + ); + } + }); + } } } } diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 920aaa65..363c5d8b 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -27,21 +27,21 @@ class InvenTreeCompany extends InvenTreeModel { InvenTreeCompany() : super(); - String get image => jsondata['image'] ?? jsondata['thumbnail'] ?? InvenTreeAPI.staticImage; + String get image => (jsondata['image'] ?? jsondata['thumbnail'] ?? InvenTreeAPI.staticImage) as String; - String get thumbnail => jsondata['thumbnail'] ?? jsondata['image'] ?? InvenTreeAPI.staticThumb; + String get thumbnail => (jsondata['thumbnail'] ?? jsondata['image'] ?? InvenTreeAPI.staticThumb) as String; - String get website => jsondata['website'] ?? ''; + String get website => (jsondata['website'] ?? '') as String; - String get phone => jsondata['phone'] ?? ''; + String get phone => (jsondata['phone'] ?? '') as String; - String get email => jsondata['email'] ?? ''; + String get email => (jsondata['email'] ?? '') as String; - bool get isSupplier => jsondata['is_supplier'] ?? false; + bool get isSupplier => (jsondata['is_supplier'] ?? false) as bool; - bool get isManufacturer => jsondata['is_manufacturer'] ?? false; + bool get isManufacturer => (jsondata['is_manufacturer'] ?? false) as bool; - bool get isCustomer => jsondata['is_customer'] ?? false; + bool get isCustomer => (jsondata['is_customer'] ?? false) as bool; InvenTreeCompany.fromJson(Map json) : super.fromJson(json); @@ -85,27 +85,27 @@ class InvenTreeSupplierPart extends InvenTreeModel { int get manufacturerId => (jsondata['manufacturer'] ?? -1) as int; - String get manufacturerName => jsondata['manufacturer_detail']['name']; + String get manufacturerName => (jsondata['manufacturer_detail']['name'] ?? '') as String; - String get manufacturerImage => jsondata['manufacturer_detail']['image'] ?? jsondata['manufacturer_detail']['thumbnail']; + String get manufacturerImage => (jsondata['manufacturer_detail']['image'] ?? jsondata['manufacturer_detail']['thumbnail'] ?? InvenTreeAPI.staticThumb) as String; int get manufacturerPartId => (jsondata['manufacturer_part'] ?? -1) as int; int get supplierId => (jsondata['supplier'] ?? -1) as int; - String get supplierName => jsondata['supplier_detail']['name']; + String get supplierName => (jsondata['supplier_detail']['name'] ?? '') as String; - String get supplierImage => jsondata['supplier_detail']['image'] ?? jsondata['supplier_detail']['thumbnail']; + String get supplierImage => (jsondata['supplier_detail']['image'] ?? jsondata['supplier_detail']['thumbnail'] ?? InvenTreeAPI.staticThumb) as String; String get SKU => (jsondata['SKU'] ?? '') as String; - String get MPN => jsondata['MPN'] ?? ''; + String get MPN => (jsondata['MPN'] ?? '') as String; - int get partId => jsondata['part'] ?? -1; + int get partId => (jsondata['part'] ?? -1) as int; - String get partImage => jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb; + String get partImage => (jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get partName => jsondata["part_detail"]["full_name"] ?? ""; + String get partName => (jsondata["part_detail"]["full_name"] ?? '') as String; @override InvenTreeModel createFromJson(Map json) { diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 50ec330f..0b9abc02 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -132,16 +132,16 @@ class InvenTreeModel { int get pk => (jsondata['pk'] ?? -1) as int; // Some common accessors - String get name => jsondata['name'] ?? ''; + String get name => (jsondata['name'] ?? '') as String; - String get description => jsondata['description'] ?? ''; + String get description => (jsondata['description'] ?? '') as String; - String get notes => jsondata['notes'] ?? ''; + String get notes => (jsondata['notes'] ?? '') as String; int get parentId => (jsondata['parent'] ?? -1) as int; // Legacy API provided external link as "URL", while newer API uses "link" - String get link => jsondata['link'] ?? jsondata['URL'] ?? ''; + String get link => (jsondata['link'] ?? jsondata['URL'] ?? '') as String; void goToInvenTreePage() async { @@ -162,7 +162,7 @@ class InvenTreeModel { } } - String get keywords => jsondata['keywords'] ?? ''; + String get keywords => (jsondata['keywords'] ?? '') as String; // Create a new object from JSON data (not a constructor!) InvenTreeModel createFromJson(Map json) { @@ -224,7 +224,7 @@ class InvenTreeModel { } - jsondata = response.data; + jsondata = response.asMap(); return true; } @@ -297,13 +297,11 @@ class InvenTreeModel { } - return createFromJson(response.data); + return createFromJson(response.asMap()); } Future create(Map data) async { - print("CREATE: ${URL} ${data.toString()}"); - if (data.containsKey('pk')) { data.remove('pk'); } @@ -340,7 +338,7 @@ class InvenTreeModel { return null; } - return createFromJson(response.data); + return createFromJson(response.asMap()); } Future listPaginated(int limit, int offset, {Map filters = const {}}) async { @@ -362,13 +360,15 @@ class InvenTreeModel { // Construct the response InvenTreePageResponse page = new InvenTreePageResponse(); - if (response.data.containsKey("count") && response.data.containsKey("results")) { - page.count = response.data["count"] as int; + var data = response.asMap(); + + if (data.containsKey("count") && data.containsKey("results")) { + page.count = (data["count"] ?? 0) as int; page.results = []; for (var result in response.data["results"]) { - page.addResult(createFromJson(result)); + page.addResult(createFromJson(result as Map)); } return page; @@ -396,20 +396,22 @@ class InvenTreeModel { return results; } - dynamic data; + List data = []; - if (response.data is List) { - data = response.data; - } else if (response.data.containsKey('results')) { - data = response.data['results']; - } else { - data = []; + if (response.isList()) { + data = response.asList(); + } else if (response.isMap()) { + var mData = response.asMap(); + + if (mData.containsKey('results')) { + data = (response.data['results'] ?? []) as List; + } } for (var d in data) { // Create a new object (of the current class type - InvenTreeModel obj = createFromJson(d); + InvenTreeModel obj = createFromJson(d as Map); results.add(obj); } @@ -460,7 +462,7 @@ class InvenTreeAttachment extends InvenTreeModel { InvenTreeAttachment() : super(); - String get attachment => jsondata["attachment"] ?? ''; + String get attachment => (jsondata["attachment"] ?? '') as String; // Return the filename of the attachment String get filename { @@ -498,11 +500,11 @@ class InvenTreeAttachment extends InvenTreeModel { return FontAwesomeIcons.fileAlt; } - String get comment => jsondata["comment"] ?? ''; + String get comment => (jsondata["comment"] ?? '') as String; DateTime? get uploadDate { if (jsondata.containsKey("upload_date")) { - return DateTime.tryParse(jsondata["upload_date"] ?? ''); + return DateTime.tryParse((jsondata["upload_date"] ?? '') as String); } else { return null; } diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 13f7911a..237695ba 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -33,7 +33,7 @@ class InvenTreePartCategory extends InvenTreeModel { return filters; } - String get pathstring => jsondata['pathstring'] ?? ''; + String get pathstring => (jsondata['pathstring'] ?? '') as String; String get parentpathstring { // TODO - Drive the refactor tractor through this @@ -52,7 +52,7 @@ class InvenTreePartCategory extends InvenTreeModel { return p; } - int get partcount => jsondata['parts'] ?? 0; + int get partcount => (jsondata['parts'] ?? 0) as int; InvenTreePartCategory() : super(); @@ -74,17 +74,17 @@ class InvenTreePartTestTemplate extends InvenTreeModel { @override String get URL => "part/test-template/"; - String get key => jsondata['key'] ?? ''; + String get key => (jsondata['key'] ?? '') as String; - String get testName => jsondata['test_name'] ?? ''; + String get testName => (jsondata['test_name'] ?? '') as String; - String get description => jsondata['description'] ?? ''; + String get description => (jsondata['description'] ?? '') as String; - bool get required => jsondata['required'] ?? false; + bool get required => (jsondata['required'] ?? false) as bool; - bool get requiresValue => jsondata['requires_value'] ?? false; + bool get requiresValue => (jsondata['requires_value'] ?? false) as bool; - bool get requiresAttachment => jsondata['requires_attachment'] ?? false; + bool get requiresAttachment => (jsondata['requires_attachment'] ?? false) as bool; InvenTreePartTestTemplate() : super(); @@ -271,7 +271,7 @@ class InvenTreePart extends InvenTreeModel { return q; } - String get units => jsondata["units"] ?? ""; + String get units => (jsondata["units"] as String) ?? ""; // Get the number of units being build for this Part double get building => double.tryParse(jsondata['building'].toString()) ?? 0; @@ -297,10 +297,10 @@ class InvenTreePart extends InvenTreeModel { bool get isTrackable => (jsondata['trackable'] ?? false) as bool; // Get the IPN (internal part number) for the Part instance - String get IPN => jsondata['IPN'] ?? ''; + String get IPN => (jsondata['IPN'] ?? '') as String; // Get the revision string for the Part instance - String get revision => jsondata['revision'] ?? ''; + String get revision => (jsondata['revision'] ?? '') as String; // Get the category ID for the Part instance (or 'null' if does not exist) int get categoryId => (jsondata['category'] ?? -1) as int; @@ -312,7 +312,7 @@ class InvenTreePart extends InvenTreeModel { if (!jsondata.containsKey('category_detail')) return ''; - return jsondata['category_detail']?['name'] ?? ''; + return (jsondata['category_detail']?['name'] ?? '') as String; } // Get the category description for the Part instance @@ -322,18 +322,18 @@ class InvenTreePart extends InvenTreeModel { if (!jsondata.containsKey('category_detail')) return ''; - return jsondata['category_detail']?['description'] ?? ''; + return (jsondata['category_detail']?['description'] ?? '') as String; } // Get the image URL for the Part instance - String get _image => jsondata['image'] ?? ''; + String get _image => (jsondata['image'] ?? '') as String; // Get the thumbnail URL for the Part instance - String get _thumbnail => jsondata['thumbnail'] ?? ''; + String get _thumbnail => (jsondata['thumbnail'] ?? '') as String; // Return the fully-qualified name for the Part instance String get fullname { - String fn = jsondata['full_name'] ?? ''; + String fn = (jsondata['full_name'] ?? '') as String; if (fn.isNotEmpty) return fn; diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 3b279485..d5c02eef 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -44,23 +44,23 @@ class InvenTreePurchaseOrder extends InvenTreeModel { }; } - String get issueDate => jsondata['issue_date'] ?? ""; + String get issueDate => (jsondata['issue_date'] ?? '') as String; - String get completeDate => jsondata['complete_date'] ?? ""; + String get completeDate => (jsondata['complete_date'] ?? '') as String; - String get creationDate => jsondata['creation_date'] ?? ""; + String get creationDate => (jsondata['creation_date'] ?? '') as String; - String get targetDate => jsondata['target_date'] ?? ""; + String get targetDate => (jsondata['target_date'] ?? '') as String; - int get lineItemCount => jsondata['line_items'] ?? 0; + int get lineItemCount => (jsondata['line_items'] ?? 0) as int; - bool get overdue => jsondata['overdue'] ?? false; + bool get overdue => (jsondata['overdue'] ?? false) as bool; - String get reference => jsondata['reference'] ?? ""; + String get reference => (jsondata['reference'] ?? '') as String; - int get responsible => jsondata['responsible'] ?? -1; + int get responsibleId => (jsondata['responsible'] ?? -1) as int; - int get supplierId => jsondata['supplier'] ?? -1; + int get supplierId => (jsondata['supplier'] ?? -1) as int; InvenTreeCompany? get supplier { @@ -69,15 +69,15 @@ class InvenTreePurchaseOrder extends InvenTreeModel { if (supplier_detail == null) { return null; } else { - return InvenTreeCompany.fromJson(supplier_detail); + return InvenTreeCompany.fromJson(supplier_detail as Map); } } - String get supplierReference => jsondata['supplier_reference'] ?? ""; + String get supplierReference => (jsondata['supplier_reference'] ?? '') as String; - int get status => jsondata['status'] ?? -1; + int get status => (jsondata['status'] ?? -1) as int; - String get statusText => jsondata['status_text'] ?? ""; + String get statusText => (jsondata['status_text'] ?? '') as String; bool get isOpen => this.status == PO_STATUS_PENDING || this.status == PO_STATUS_PLACED; @@ -146,25 +146,25 @@ class InvenTreePOLineItem extends InvenTreeModel { bool get isComplete => received >= quantity; - double get quantity => jsondata['quantity'] ?? 0; + double get quantity => (jsondata['quantity'] ?? 0) as double; - double get received => jsondata['received'] ?? 0; + double get received => (jsondata['received'] ?? 0) as double; double get outstanding => quantity - received; - String get reference => jsondata['reference'] ?? ""; + String get reference => (jsondata['reference'] ?? '') as String; - int get orderId => jsondata['order'] ?? -1; + int get orderId => (jsondata['order'] ?? -1) as int; - int get supplirtPartId => jsondata['part'] ?? -1; + int get supplierPartId => (jsondata['part'] ?? -1) as int; InvenTreePart? get part { - dynamic part_detail = jsondata["part_detail"] ?? null; + dynamic part_detail = jsondata["part_detail"]; if (part_detail == null) { return null; } else { - return InvenTreePart.fromJson(part_detail); + return InvenTreePart.fromJson(part_detail as Map); } } @@ -175,19 +175,19 @@ class InvenTreePOLineItem extends InvenTreeModel { if (detail == null) { return null; } else { - return InvenTreeSupplierPart.fromJson(detail); + return InvenTreeSupplierPart.fromJson(detail as Map); } } - double get purchasePrice => double.parse(jsondata['purchase_price']); + double get purchasePrice => double.parse((jsondata['purchase_price'] ?? '') as String); - String get purchasePriceCurrency => jsondata['purchase_price_currency'] ?? ""; + String get purchasePriceCurrency => (jsondata['purchase_price_currency'] ?? '') as String; - String get purchasePriceString => jsondata['purchase_price_string'] ?? ""; + String get purchasePriceString => (jsondata['purchase_price_string'] ?? '') as String; - int get destination => jsondata['destination'] ?? -1; + int get destination => (jsondata['destination'] ?? -1) as int; - Map get destinationDetail => jsondata['destination_detail']; + Map get destinationDetail => (jsondata['destination_detail'] ?? {}) as Map; InvenTreePOLineItem() : super(); diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index ad743f8f..56b54d73 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -31,19 +31,19 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { }; } - String get key => jsondata['key'] ?? ''; + String get key => (jsondata['key'] ?? '') as String; - String get testName => jsondata['test'] ?? ''; + String get testName => (jsondata['test'] ?? '') as String; - bool get result => jsondata['result'] ?? false; + bool get result => (jsondata['result'] ?? false) as bool; - String get value => jsondata['value'] ?? ''; + String get value => (jsondata['value'] ?? '') as String; - String get notes => jsondata['notes'] ?? ''; + String get notes => (jsondata['notes'] ?? '') as String; - String get attachment => jsondata['attachment'] ?? ''; + String get attachment => (jsondata['attachment'] ?? '') as String; - String get date => jsondata['date'] ?? ''; + String get date => (jsondata['date'] ?? '') as String; InvenTreeStockItemTestResult() : super(); @@ -204,17 +204,17 @@ class InvenTreeStockItem extends InvenTreeModel { }); } - String get uid => jsondata['uid'] ?? ''; + String get uid => (jsondata['uid'] ?? '') as String; - int get status => jsondata['status'] ?? -1; + int get status => (jsondata['status'] ?? -1) as int; - String get packaging => jsondata["packaging"] ?? ""; + String get packaging => (jsondata["packaging"] ?? '') as String; - String get batch => jsondata["batch"] ?? ""; + String get batch => (jsondata["batch"] ?? '') as String; - int get partId => jsondata['part'] ?? -1; + int get partId => (jsondata['part'] ?? -1) as int; - String get purchasePrice => jsondata['purchase_price'] ?? ""; + String get purchasePrice => (jsondata['purchase_price'] ?? '') as String; bool get hasPurchasePrice { @@ -223,14 +223,14 @@ class InvenTreeStockItem extends InvenTreeModel { return pp.isNotEmpty && pp.trim() != "-"; } - int get purchaseOrderId => jsondata['purchase_order'] ?? -1; + int get purchaseOrderId => (jsondata['purchase_order'] ?? -1) as int; int get trackingItemCount => (jsondata['tracking_items'] ?? 0) as int; // Date of last update DateTime? get updatedDate { if (jsondata.containsKey("updated")) { - return DateTime.tryParse(jsondata["updated"] ?? ''); + return DateTime.tryParse((jsondata["updated"] ?? '') as String); } else { return null; } @@ -250,7 +250,7 @@ class InvenTreeStockItem extends InvenTreeModel { DateTime? get stocktakeDate { if (jsondata.containsKey("stocktake_date")) { - return DateTime.tryParse(jsondata["stocktake_date"] ?? ''); + return DateTime.tryParse((jsondata["stocktake_date"] ?? '') as String); } else { return null; } @@ -274,12 +274,12 @@ class InvenTreeStockItem extends InvenTreeModel { // Use the detailed part information as priority if (jsondata.containsKey('part_detail')) { - nm = jsondata['part_detail']['full_name'] ?? ''; + nm = (jsondata['part_detail']['full_name'] ?? '') as String; } // Backup if first value fails if (nm.isEmpty) { - nm = jsondata['part__name'] ?? ''; + nm = (jsondata['part__name'] ?? '') as String; } return nm; @@ -290,11 +290,11 @@ class InvenTreeStockItem extends InvenTreeModel { // Use the detailed part description as priority if (jsondata.containsKey('part_detail')) { - desc = jsondata['part_detail']['description'] ?? ''; + desc = (jsondata['part_detail']['description'] ?? '') as String; } if (desc.isEmpty) { - desc = jsondata['part__description'] ?? ''; + desc = (jsondata['part__description'] ?? '') as String; } return desc; @@ -304,11 +304,11 @@ class InvenTreeStockItem extends InvenTreeModel { String img = ''; if (jsondata.containsKey('part_detail')) { - img = jsondata['part_detail']['thumbnail'] ?? ''; + img = (jsondata['part_detail']['thumbnail'] ?? '') as String; } if (img.isEmpty) { - img = jsondata['part__thumbnail'] ?? ''; + img = (jsondata['part__thumbnail'] ?? '') as String; } return img; @@ -321,16 +321,16 @@ class InvenTreeStockItem extends InvenTreeModel { String thumb = ""; - thumb = jsondata['part_detail']?['thumbnail'] ?? ''; + thumb = (jsondata['part_detail']?['thumbnail'] ?? '') as String; // Use 'image' as a backup if (thumb.isEmpty) { - thumb = jsondata['part_detail']?['image'] ?? ''; + thumb = (jsondata['part_detail']?['image'] ?? '') as String; } // Try a different approach if (thumb.isEmpty) { - thumb = jsondata['part__thumbnail'] ?? ''; + thumb = (jsondata['part__thumbnail'] ?? '') as String; } // Still no thumbnail? Use the 'no image' image @@ -345,7 +345,7 @@ class InvenTreeStockItem extends InvenTreeModel { String thumb = ''; if (jsondata.containsKey("supplier_detail")) { - thumb = jsondata['supplier_detail']['supplier_logo'] ?? ''; + thumb = (jsondata['supplier_detail']['supplier_logo'] ?? '') as String; } return thumb; @@ -355,27 +355,27 @@ class InvenTreeStockItem extends InvenTreeModel { String sname = ''; if (jsondata.containsKey("supplier_detail")) { - sname = jsondata["supplier_detail"]["supplier_name"] ?? ''; + sname = (jsondata["supplier_detail"]["supplier_name"] ?? '') as String; } return sname; } String get units { - return jsondata['part_detail']?['units'] ?? ''; + return (jsondata['part_detail']?['units'] ?? '') as String; } String get supplierSKU { String sku = ''; if (jsondata.containsKey("supplier_detail")) { - sku = jsondata["supplier_detail"]["SKU"] ?? ''; + sku = (jsondata["supplier_detail"]["SKU"] ?? '') as String; } return sku; } - String get serialNumber => jsondata['serial'] ?? ""; + String get serialNumber => (jsondata['serial'] ?? '') as String; double get quantity => double.tryParse(jsondata['quantity'].toString()) ?? 0; @@ -417,11 +417,11 @@ class InvenTreeStockItem extends InvenTreeModel { if (locationId == -1 || !jsondata.containsKey('location_detail')) return 'Unknown Location'; - loc = jsondata['location_detail']['name'] ?? ''; + loc = (jsondata['location_detail']['name'] ?? '') as String; // Old-style name if (loc.isEmpty) { - loc = jsondata['location__name'] ?? ''; + loc = (jsondata['location__name'] ?? '') as String; } return loc; @@ -431,7 +431,7 @@ class InvenTreeStockItem extends InvenTreeModel { if (locationId == -1 || !jsondata.containsKey('location_detail')) return L10().locationNotSet; - String _loc = jsondata['location_detail']['pathstring'] ?? ''; + String _loc = (jsondata['location_detail']['pathstring'] ?? '') as String; if (_loc.isNotEmpty) { return _loc; @@ -540,7 +540,7 @@ class InvenTreeStockLocation extends InvenTreeModel { @override String get URL => "stock/location/"; - String get pathstring => jsondata['pathstring'] ?? ''; + String get pathstring => (jsondata['pathstring'] ?? '') as String; @override Map formFields() { @@ -568,7 +568,7 @@ class InvenTreeStockLocation extends InvenTreeModel { return p; } - int get itemcount => jsondata['items'] ?? 0; + int get itemcount => (jsondata['items'] ?? 0) as int; InvenTreeStockLocation() : super(); diff --git a/lib/preferences.dart b/lib/preferences.dart index f6e2372f..645f614d 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -34,7 +34,7 @@ class InvenTreePreferencesDB { return _dbOpenCompleter.future; } - Future _openDatabase() async { + Future _openDatabase() async { // Get a platform-specific directory where persistent app data can be stored final appDocumentDir = await getApplicationDocumentsDirectory(); diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index 0f0938b9..bf175452 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -45,7 +45,7 @@ class _InvenTreeAppSettingsState extends State { void setBarcodeSounds(bool en) async { await InvenTreeSettingsManager().setValue("barcodeSounds", en); - barcodeSounds = await InvenTreeSettingsManager().getValue("barcodeSounds", true); + barcodeSounds = await InvenTreeSettingsManager().getBool("barcodeSounds", true); setState(() { }); @@ -54,7 +54,7 @@ class _InvenTreeAppSettingsState extends State { void setServerSounds(bool en) async { await InvenTreeSettingsManager().setValue("serverSounds", en); - serverSounds = await InvenTreeSettingsManager().getValue("serverSounds", true); + serverSounds = await InvenTreeSettingsManager().getBool("serverSounds", true); setState(() { }); @@ -62,7 +62,7 @@ class _InvenTreeAppSettingsState extends State { void setPartSubcategory(bool en) async { await InvenTreeSettingsManager().setValue("partSubcategory", en); - partSubcategory = await InvenTreeSettingsManager().getValue("partSubcategory", true); + partSubcategory = await InvenTreeSettingsManager().getBool("partSubcategory", true); setState(() { }); @@ -70,7 +70,7 @@ class _InvenTreeAppSettingsState extends State { void setStockSublocation(bool en) async { await InvenTreeSettingsManager().setValue("stockSublocation", en); - stockSublocation = await InvenTreeSettingsManager().getValue("stockSublocation", true); + stockSublocation = await InvenTreeSettingsManager().getBool("stockSublocation", true); setState(() { }); diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 3bacea7f..7edf6fa0 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -40,17 +40,6 @@ class _InvenTreeLoginSettingsState extends State { void _editProfile(BuildContext context, {UserProfile? userProfile, bool createNew = false}) { - var _name; - var _server; - var _username; - var _password; - - UserProfile? profile; - - if (userProfile != null) { - profile = userProfile; - } - Navigator.push( context, MaterialPageRoute( diff --git a/lib/user_profile.dart b/lib/user_profile.dart index 3b6bf8f2..b0efd89a 100644 --- a/lib/user_profile.dart +++ b/lib/user_profile.dart @@ -38,10 +38,10 @@ class UserProfile { factory UserProfile.fromJson(int key, Map json, bool isSelected) => UserProfile( key: key, - name: json['name'], - server: json['server'], - username: json['username'], - password: json['password'], + name: json['name'] as String, + server: json['server'] as String, + username: json['username'] as String, + password: json['password'] as String, selected: isSelected, ); @@ -73,7 +73,7 @@ class UserProfileDBManager { return profiles.length > 0; } - Future addProfile(UserProfile profile) async { + Future addProfile(UserProfile profile) async { // Check if a profile already exists with the name final bool exists = await profileNameExists(profile.name); @@ -83,7 +83,7 @@ class UserProfileDBManager { return; } - int key = await store.add(await _db, profile.toJson()); + int key = await store.add(await _db, profile.toJson()) as int; print("Added user profile <${key}> - '${profile.name}'"); @@ -91,7 +91,7 @@ class UserProfileDBManager { profile.key = key; } - Future selectProfile(int key) async { + Future selectProfile(int key) async { /* * Mark the particular profile as selected */ @@ -101,7 +101,7 @@ class UserProfileDBManager { return result; } - Future updateProfile(UserProfile profile) async { + Future updateProfile(UserProfile profile) async { if (profile.key == null) { await addProfile(profile); @@ -115,7 +115,7 @@ class UserProfileDBManager { return result; } - Future deleteProfile(UserProfile profile) async { + Future deleteProfile(UserProfile profile) async { await store.record(profile.key).delete(await _db); print("Deleted user profile <${profile.key}> - '${profile.name}'"); } @@ -135,8 +135,8 @@ class UserProfileDBManager { if (profiles[idx].key is int && profiles[idx].key == selected) { return UserProfile.fromJson( - profiles[idx].key, - profiles[idx].value, + profiles[idx].key as int, + profiles[idx].value as Map, profiles[idx].key == selected, ); } @@ -161,8 +161,8 @@ class UserProfileDBManager { if (profiles[idx].key is int) { profileList.add( UserProfile.fromJson( - profiles[idx].key, - profiles[idx].value, + profiles[idx].key as int, + profiles[idx].value as Map, profiles[idx].key == selected, )); } diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index ea4e2e30..b0faa09a 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -224,8 +224,10 @@ class _CategoryDisplayState extends RefreshableState { data: { "parent": (pk > 0) ? pk : null, }, - onSuccess: (data) async { - + onSuccess: (result) async { + + Map data = result as Map; + if (data.containsKey("pk")) { var cat = InvenTreePartCategory.fromJson(data); @@ -252,7 +254,9 @@ class _CategoryDisplayState extends RefreshableState { data: { "category": (pk > 0) ? pk : null }, - onSuccess: (data) async { + onSuccess: (result) async { + + Map data = result as Map; if (data.containsKey("pk")) { var part = InvenTreePart.fromJson(data); @@ -440,7 +444,8 @@ class _PaginatedPartListState extends State { params["search"] = _searchTerm; - final bool cascade = await InvenTreeSettingsManager().getValue("partSubcategory", true); + final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); + params["cascade"] = "${cascade}"; final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params); diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 26e8f3b6..16d9a9a8 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -146,7 +146,10 @@ class _LocationDisplayState extends RefreshableState { data: { "parent": (pk > 0) ? pk : null, }, - onSuccess: (data) async { + onSuccess: (result) async { + + Map data = result as Map; + if (data.containsKey("pk")) { var loc = InvenTreeStockLocation.fromJson(data); @@ -175,7 +178,10 @@ class _LocationDisplayState extends RefreshableState { data: { "location": pk, }, - onSuccess: (data) async { + onSuccess: (result) async { + + Map data = result as Map; + if (data.containsKey("pk")) { var item = InvenTreeStockItem.fromJson(data); @@ -515,7 +521,8 @@ class _PaginatedStockListState extends State { params["search"] = "${_searchTerm}"; // Do we include stock items from sub-locations? - final bool cascade = await InvenTreeSettingsManager().getValue("stockSublocation", true); + final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); + params["cascade"] = "${cascade}"; final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 5d52736e..778a8d70 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -405,7 +405,10 @@ class _PartDisplayState extends RefreshableState { data: { "part": "${part.pk}", }, - onSuccess: (data) async { + onSuccess: (result) async { + + Map data = result as Map; + if (data.containsKey("pk")) { var item = InvenTreeStockItem.fromJson(data); diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 862d9628..343013df 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -349,13 +349,13 @@ class _StockItemDisplayState extends RefreshableState { hint: L10().searchLocation, onChanged: null, itemAsString: (dynamic location) { - return location['pathstring']; + return (location['pathstring'] ?? '') as String; }, onSaved: (dynamic location) { if (location == null) { location_pk = null; } else { - location_pk = location['pk']; + location_pk = location['pk'] as int; } }, isFilteredOnline: true, From ad0cc36540f0fe128ed7b229f69392c3178e8180 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 20:24:55 +1000 Subject: [PATCH 23/70] More linting work --- analysis_options.yaml | 48 +++++++- lib/api.dart | 147 ++++++++++++------------ lib/api_form.dart | 90 +++++++-------- lib/app_colors.dart | 2 +- lib/app_settings.dart | 4 +- lib/barcode.dart | 76 ++++++------ lib/generated/i18n.dart | 6 +- lib/inventree/company.dart | 53 +++++---- lib/inventree/model.dart | 79 +++++++------ lib/inventree/part.dart | 92 ++++++++------- lib/inventree/purchase_order.dart | 58 +++++----- lib/inventree/sentry.dart | 62 +++++----- lib/inventree/stock.dart | 134 +++++++++++---------- lib/l10.dart | 8 +- lib/main.dart | 61 +++++----- lib/preferences.dart | 16 +-- lib/settings/about.dart | 24 ++-- lib/settings/app_settings.dart | 20 ++-- lib/settings/login.dart | 35 +++--- lib/settings/release.dart | 12 +- lib/settings/settings.dart | 36 +++--- lib/user_profile.dart | 14 +-- lib/widget/category_display.dart | 42 +++---- lib/widget/company_detail.dart | 20 ++-- lib/widget/company_list.dart | 20 ++-- lib/widget/dialogs.dart | 16 +-- lib/widget/drawer.dart | 24 ++-- lib/widget/fields.dart | 22 ++-- lib/widget/home.dart | 38 +++--- lib/widget/location_display.dart | 48 ++++---- lib/widget/paginator.dart | 12 +- lib/widget/part_attachments_widget.dart | 28 ++--- lib/widget/part_detail.dart | 50 ++++---- lib/widget/part_image_widget.dart | 28 +++-- lib/widget/part_notes.dart | 20 ++-- lib/widget/part_suppliers.dart | 20 ++-- lib/widget/progress.dart | 2 +- lib/widget/purchase_order_detail.dart | 27 +++-- lib/widget/purchase_order_list.dart | 27 +++-- lib/widget/refreshable_state.dart | 9 +- lib/widget/search.dart | 30 ++--- lib/widget/snacks.dart | 10 +- lib/widget/spinner.dart | 8 +- lib/widget/starred_parts.dart | 20 ++-- lib/widget/stock_detail.dart | 87 +++++++------- lib/widget/stock_item_test_results.dart | 39 +++---- lib/widget/stock_notes.dart | 20 ++-- lib/widget/submit_feedback.dart | 18 ++- pubspec.yaml | 45 ++++---- test/widget_test.dart | 4 +- 50 files changed, 904 insertions(+), 907 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2ea080d3..92962f4e 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,7 +1,9 @@ include: package:lint/analysis_options.yaml analyzer: - exclude: [build/**] + exclude: + - [build/**] + - lib/generated/** language: strict-raw-types: true strong-mode: @@ -14,10 +16,44 @@ linter: # Turn off what you don't like. # # ------------------------------------- # - # Make constructors the first thing in every class - sort_constructors_first: true + # Make constructors the first thing in every class + sort_constructors_first: true - prefer_single_quotes: true + prefer_double_quotes: true - # Blindly follow the Flutter code style, which prefers types everywhere - always_specify_types: true + prefer_final_locals: false + + prefer_const_constructors: false + + prefer_final_in_for_each: false + + use_build_context_synchronously: false + + avoid_redundant_argument_values: false + + unnecessary_brace_in_string_interps: false + + unnecessary_string_interpolations: false + + no_logic_in_create_state: false + + parameter_assignments: false + + non_constant_identifier_names: false + + constant_identifier_names: false + + package_prefixed_library_names: false + + prefer_const_literals_to_create_immutables: false + + avoid_print: false + + avoid_positional_boolean_parameters: false + + prefer_final_fields: false + + sort_child_properties_last: false + + # Blindly follow the Flutter code style, which prefers types everywhere + always_specify_types: false diff --git a/lib/api.dart b/lib/api.dart index acd02f51..b89a95fa 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -1,25 +1,25 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; +import "dart:async"; +import "dart:convert"; +import "dart:io"; -import 'package:flutter/foundation.dart'; -import 'package:http/http.dart' as http; -import 'package:intl/intl.dart'; -import 'package:inventree/app_colors.dart'; +import "package:flutter/foundation.dart"; +import "package:http/http.dart" as http; +import "package:intl/intl.dart"; +import "package:inventree/app_colors.dart"; -import 'package:open_file/open_file.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:cached_network_image/cached_network_image.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:flutter_cache_manager/flutter_cache_manager.dart'; +import "package:open_file/open_file.dart"; +import "package:flutter/cupertino.dart"; +import "package:cached_network_image/cached_network_image.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:flutter_cache_manager/flutter_cache_manager.dart"; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/l10.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/user_profile.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:path_provider/path_provider.dart'; +import "package:inventree/widget/dialogs.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/user_profile.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:path_provider/path_provider.dart"; /* @@ -158,11 +158,12 @@ class InvenTreeAPI { String _makeUrl(String url) { // Strip leading slash - if (url.startsWith('/')) { + if (url.startsWith("/")) { url = url.substring(1, url.length); } - url = url.replaceAll('//', '/'); + // Prevent double-slash + url = url.replaceAll("//", "/"); return baseUrl + url; } @@ -175,7 +176,7 @@ class InvenTreeAPI { if (endpoint.startsWith("/api/") || endpoint.startsWith("api/")) { return _makeUrl(endpoint); } else { - return _makeUrl("/api/" + endpoint); + return _makeUrl("/api/${endpoint}"); } } @@ -210,10 +211,10 @@ class InvenTreeAPI { } // Server instance information - String instance = ''; + String instance = ""; // Server version information - String _version = ''; + String _version = ""; // API version of the connected server int _apiVersion = 1; @@ -272,8 +273,8 @@ class InvenTreeAPI { return false; } - if (!address.endsWith('/')) { - address = address + '/'; + if (!address.endsWith("/")) { + address = address + "/"; } /* TODO: Better URL validation * - If not a valid URL, return error @@ -307,11 +308,11 @@ class InvenTreeAPI { } // Record server information - _version = (data["version"] ?? '') as String; - instance = (data['instance'] ?? '') as String; + _version = (data["version"] ?? "") as String; + instance = (data["instance"] ?? "") as String; // Default API version is 1 if not provided - _apiVersion = (data['apiVersion'] ?? 1) as int; + _apiVersion = (data["apiVersion"] ?? 1) as int; if (_apiVersion < _minApiVersion) { @@ -388,7 +389,7 @@ class InvenTreeAPI { _connected = false; _connecting = false; - _token = ''; + _token = ""; profile = null; } @@ -435,7 +436,7 @@ class InvenTreeAPI { // Next we request the permissions assigned to the current user // Note: 2021-02-27 this "roles" feature for the API was just introduced. - // Any 'older' version of the server allows any API method for any logged in user! + // Any "older" version of the server allows any API method for any logged in user! // We will return immediately, but request the user roles in the background var response = await get(_URL_GET_ROLES, expectedStatusCode: 200); @@ -446,9 +447,9 @@ class InvenTreeAPI { var data = response.asMap(); - if (data.containsKey('roles')) { + if (data.containsKey("roles")) { // Save a local copy of the user roles - roles = response.data['roles'] as Map; + roles = response.data["roles"] as Map; } } @@ -456,7 +457,7 @@ class InvenTreeAPI { /* * Check if the user has the given role.permission assigned *e - * e.g. 'part', 'change' + * e.g. "part", "change" */ // If we do not have enough information, assume permission is allowed @@ -489,9 +490,9 @@ class InvenTreeAPI { if (request == null) { // Return an "invalid" APIResponse - return new APIResponse( + return APIResponse( url: url, - method: 'PATCH', + method: "PATCH", error: "HttpClientRequest is null" ); } @@ -535,7 +536,7 @@ class InvenTreeAPI { HttpClientRequest? _request; - var client = createClient(true); + var client = createClient(allowBadCert: true); // Attempt to open a connection to the server try { @@ -543,8 +544,8 @@ class InvenTreeAPI { // Set headers _request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); - _request.headers.set(HttpHeaders.acceptHeader, 'application/json'); - _request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); + _request.headers.set(HttpHeaders.acceptHeader, "application/json"); + _request.headers.set(HttpHeaders.contentTypeHeader, "application/json"); _request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); } on SocketException catch (error) { @@ -684,9 +685,9 @@ class InvenTreeAPI { if (request == null) { // Return an "invalid" APIResponse - return new APIResponse( + return APIResponse( url: url, - method: 'OPTIONS' + method: "OPTIONS" ); } @@ -703,9 +704,9 @@ class InvenTreeAPI { if (request == null) { // Return an "invalid" APIResponse - return new APIResponse( + return APIResponse( url: url, - method: 'POST' + method: "POST" ); } @@ -716,15 +717,13 @@ class InvenTreeAPI { ); } - HttpClient createClient(bool allowBadCert) { + HttpClient createClient({bool allowBadCert = true}) { - var client = new HttpClient(); + var client = HttpClient(); - client.badCertificateCallback = ((X509Certificate cert, String host, int port) { + client.badCertificateCallback = (X509Certificate cert, String host, int port) { // TODO - Introspection of actual certificate? - allowBadCert = true; - if (allowBadCert) { return true; } else { @@ -734,7 +733,7 @@ class InvenTreeAPI { ); return false; } - }); + }; // Set the connection timeout client.connectionTimeout = Duration(seconds: 30); @@ -746,7 +745,7 @@ class InvenTreeAPI { * Initiate a HTTP request to the server * * @param url is the API endpoint - * @param method is the HTTP method e.g. 'POST' / 'PATCH' / 'GET' etc; + * @param method is the HTTP method e.g. "POST" / "PATCH" / "GET" etc; * @param params is the request parameters */ Future apiRequest(String url, String method, {Map urlParams = const {}}) async { @@ -763,7 +762,7 @@ class InvenTreeAPI { } // Remove extraneous character if present - if (_url.endsWith('&')) { + if (_url.endsWith("&")) { _url = _url.substring(0, _url.length - 1); } @@ -781,7 +780,7 @@ class InvenTreeAPI { HttpClientRequest? _request; - var client = createClient(true); + var client = createClient(allowBadCert: true); // Attempt to open a connection to the server try { @@ -789,8 +788,8 @@ class InvenTreeAPI { // Set headers _request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); - _request.headers.set(HttpHeaders.acceptHeader, 'application/json'); - _request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); + _request.headers.set(HttpHeaders.acceptHeader, "application/json"); + _request.headers.set(HttpHeaders.contentTypeHeader, "application/json"); _request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); return _request; @@ -824,7 +823,7 @@ class InvenTreeAPI { request.add(encoded_data); } - APIResponse response = new APIResponse( + APIResponse response = APIResponse( method: request.method, url: request.uri.toString() ); @@ -837,6 +836,19 @@ class InvenTreeAPI { // If the server returns a server error code, alert the user if (_response.statusCode >= 500) { showStatusCodeError(_response.statusCode); + + sentryReportMessage( + "Server error", + context: { + "url": request.uri.toString(), + "method": request.method, + "statusCode": _response.statusCode.toString(), + "requestHeaders": request.headers.toString(), + "responseHeaders": _response.headers.toString(), + "responseData": response.data.toString(), + } + ); + } else { response.data = await responseToJson(_response) ?? {}; @@ -846,21 +858,6 @@ class InvenTreeAPI { if (statusCode != _response.statusCode) { showStatusCodeError(_response.statusCode); } - - // Report any server errors - if (_response.statusCode >= 500) { - sentryReportMessage( - "Server error", - context: { - "url": request.uri.toString(), - "method": request.method, - "statusCode": _response.statusCode.toString(), - "requestHeaders": request.headers.toString(), - "responseHeaders": _response.headers.toString(), - "responseData": response.data.toString(), - } - ); - } } } @@ -930,9 +927,9 @@ class InvenTreeAPI { if (request == null) { // Return an "invalid" APIResponse - return new APIResponse( + return APIResponse( url: url, - method: 'GET', + method: "GET", error: "HttpClientRequest is null", ); } @@ -945,8 +942,8 @@ class InvenTreeAPI { var headers = Map(); headers[HttpHeaders.authorizationHeader] = _authorizationHeader(); - headers[HttpHeaders.acceptHeader] = 'application/json'; - headers[HttpHeaders.contentTypeHeader] = 'application/json'; + headers[HttpHeaders.acceptHeader] = "application/json"; + headers[HttpHeaders.contentTypeHeader] = "application/json"; headers[HttpHeaders.acceptLanguageHeader] = Intl.getCurrentLocale(); return headers; @@ -956,7 +953,7 @@ class InvenTreeAPI { if (_token.isNotEmpty) { return "Token $_token"; } else if (profile != null) { - return "Basic " + base64Encode(utf8.encode('${profile?.username}:${profile?.password}')); + return "Basic " + base64Encode(utf8.encode("${profile?.username}:${profile?.password}")); } else { return ""; } diff --git a/lib/api_form.dart b/lib/api_form.dart index 1b8b472b..93fd5171 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -1,22 +1,22 @@ -import 'dart:ui'; -import 'dart:io'; +import "dart:ui"; +import "dart:io"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:date_field/date_field.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:dropdown_search/dropdown_search.dart"; +import "package:date_field/date_field.dart"; -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/dialogs.dart"; +import "package:inventree/widget/fields.dart"; +import "package:inventree/l10.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:inventree/widget/snacks.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:inventree/widget/snacks.dart"; @@ -41,24 +41,24 @@ class APIFormField { dynamic initial_data; // Get the "api_url" associated with a related field - String get api_url => (data["api_url"] ?? '') as String; + String get api_url => (data["api_url"] ?? "") as String; // Get the "model" associated with a related field - String get model => (data["model"] ?? '') as String; + String get model => (data["model"] ?? "") as String; // Is this field hidden? - bool get hidden => (data['hidden'] ?? false) as bool; + bool get hidden => (data["hidden"] ?? false) as bool; // Is this field read only? - bool get readOnly => (data['read_only'] ?? false) as bool; + bool get readOnly => (data["read_only"] ?? false) as bool; - bool get multiline => (data['multiline'] ?? false) as bool; + bool get multiline => (data["multiline"] ?? false) as bool; // Get the "value" as a string (look for "default" if not available) - dynamic get value => (data['value'] ?? data['default']); + dynamic get value => (data["value"] ?? data["default"]); // Get the "default" as a string - dynamic get defaultValue => data['default']; + dynamic get defaultValue => data["default"]; Map get filters { @@ -96,7 +96,7 @@ class APIFormField { // Return the error message associated with this field List errorMessages() { - List errors = (data['errors'] ?? []) as List; + List errors = (data["errors"] ?? []) as List; List messages = []; @@ -108,15 +108,15 @@ class APIFormField { } // Is this field required? - bool get required => (data['required'] ?? false) as bool; + bool get required => (data["required"] ?? false) as bool; - String get type => (data['type'] ?? '').toString(); + String get type => (data["type"] ?? "").toString(); - String get label => (data['label'] ?? '').toString(); + String get label => (data["label"] ?? "").toString(); - String get helpText => (data['help_text'] ?? '').toString(); + String get helpText => (data["help_text"] ?? "").toString(); - String get placeholderText => (data['placeholder'] ?? '').toString(); + String get placeholderText => (data["placeholder"] ?? "").toString(); List get choices => (data["choices"] ?? []) as List; @@ -193,13 +193,13 @@ class APIFormField { labelText: label, labelStyle: _labelStyle(), ), - initialValue: DateTime.tryParse((value ?? '') as String), + initialValue: DateTime.tryParse((value ?? "") as String), autovalidateMode: AutovalidateMode.always, validator: (e) { // TODO }, onDateSelected: (DateTime dt) { - data['value'] = dt.toString().split(" ").first; + data["value"] = dt.toString().split(" ").first; }, ); @@ -250,7 +250,7 @@ class APIFormField { // Check if the current value is within the allowed values for (var opt in choices) { - if (opt['value'] == value) { + if (opt["value"] == value) { _initial = opt; break; } @@ -267,13 +267,13 @@ class APIFormField { autoFocusSearchBox: true, showClearButton: !required, itemAsString: (dynamic item) { - return (item['display_name'] ?? '') as String; + return (item["display_name"] ?? "") as String; }, onSaved: (item) { if (item == null) { - data['value'] = null; + data["value"] = null; } else { - data['value'] = item['value']; + data["value"] = item["value"]; } } ); @@ -294,7 +294,7 @@ class APIFormField { keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true), validator: (value) { - double? quantity = double.tryParse(value.toString()) ?? null; + double? quantity = double.tryParse(value.toString()); if (quantity == null) { return L10().numberInvalid; @@ -335,7 +335,7 @@ class APIFormField { List results = []; - for (var result in response.data['results'] ?? []) { + for (var result in response.data["results"] ?? []) { results.add(result); } @@ -371,9 +371,9 @@ class APIFormField { }, onSaved: (item) { if (item != null) { - data['value'] = item['pk'] ?? null; + data["value"] = item["pk"]; } else { - data['value'] = null; + data["value"] = null; } }, isFilteredOnline: true, @@ -386,7 +386,7 @@ class APIFormField { return false; } - return item['pk'] == selectedItem['pk']; + return item["pk"] == selectedItem["pk"]; } ); } @@ -443,7 +443,7 @@ class APIFormField { ) : null, ); case "owner": - String name = (item["name"] ?? '') as String; + String name = (item["name"] ?? "") as String; bool isGroup = (item["label"] ?? "") == "group"; return ListTile( title: Text(name), @@ -478,7 +478,7 @@ class APIFormField { readOnly: readOnly, maxLines: multiline ? null : 1, expands: false, - initialValue: (value ?? '') as String, + initialValue: (value ?? "") as String, onSaved: (val) { data["value"] = val; }, @@ -500,7 +500,7 @@ class APIFormField { helperStyle: _helperStyle(), initial: value as bool, onSaved: (val) { - data['value'] = val; + data["value"] = val; }, ); } @@ -631,7 +631,7 @@ Future launchApiForm(BuildContext context, String title, String url, Map { // Update field errors for (var field in fields) { - field.data['errors'] = response.data[field.name]; + field.data["errors"] = response.data[field.name]; } break; // TODO: Other status codes? diff --git a/lib/app_colors.dart b/lib/app_colors.dart index 36b7ee01..99d81384 100644 --- a/lib/app_colors.dart +++ b/lib/app_colors.dart @@ -1,6 +1,6 @@ -import 'dart:ui'; +import "dart:ui"; const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1); const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1); diff --git a/lib/app_settings.dart b/lib/app_settings.dart index 5946249a..c2c672d8 100644 --- a/lib/app_settings.dart +++ b/lib/app_settings.dart @@ -2,8 +2,8 @@ * Class for managing app-level configuration options */ -import 'package:sembast/sembast.dart'; -import 'package:inventree/preferences.dart'; +import "package:sembast/sembast.dart"; +import "package:inventree/preferences.dart"; class InvenTreeSettingsManager { diff --git a/lib/barcode.dart b/lib/barcode.dart index 60be2aaa..e3069dc3 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -1,26 +1,26 @@ -import 'package:inventree/app_settings.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:audioplayers/audioplayers.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:one_context/one_context.dart'; +import "dart:io"; -import 'package:qr_code_scanner/qr_code_scanner.dart'; +import "package:inventree/app_settings.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/dialogs.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:audioplayers/audioplayers.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:one_context/one_context.dart"; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/l10.dart'; +import "package:qr_code_scanner/qr_code_scanner.dart"; -import 'package:inventree/api.dart'; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/l10.dart"; -import 'package:inventree/widget/location_display.dart'; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/stock_detail.dart'; +import "package:inventree/api.dart"; -import 'dart:io'; +import "package:inventree/widget/location_display.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/stock_detail.dart"; class BarcodeHandler { @@ -38,7 +38,7 @@ class BarcodeHandler { QRViewController? _controller; - void successTone() async { + Future successTone() async { final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; @@ -48,7 +48,7 @@ class BarcodeHandler { } } - void failureTone() async { + Future failureTone() async { final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; @@ -120,9 +120,9 @@ class BarcodeHandler { "errorDetail": response.errorDetail, } ); - } else if (data.containsKey('error')) { + } else if (data.containsKey("error")) { onBarcodeUnknown(context, data); - } else if (data.containsKey('success')) { + } else if (data.containsKey("success")) { onBarcodeMatched(context, data); } else { onBarcodeUnhandled(context, data); @@ -158,9 +158,9 @@ class BarcodeScanHandler extends BarcodeHandler { int pk = -1; // A stocklocation has been passed? - if (data.containsKey('stocklocation')) { + if (data.containsKey("stocklocation")) { - pk = (data['stocklocation']?['pk'] ?? -1) as int; + pk = (data["stocklocation"]?["pk"] ?? -1) as int; if (pk > 0) { @@ -182,9 +182,9 @@ class BarcodeScanHandler extends BarcodeHandler { ); } - } else if (data.containsKey('stockitem')) { + } else if (data.containsKey("stockitem")) { - pk = (data['stockitem']?['pk'] ?? -1) as int; + pk = (data["stockitem"]?["pk"] ?? -1) as int; if (pk > 0) { @@ -208,9 +208,9 @@ class BarcodeScanHandler extends BarcodeHandler { success: false ); } - } else if (data.containsKey('part')) { + } else if (data.containsKey("part")) { - pk = (data['part']?['pk'] ?? -1) as int; + pk = (data["part"]?["pk"] ?? -1) as int; if (pk > 0) { @@ -278,7 +278,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { failureTone(); - // If the barcode is known, we can't assign it to the stock item! + // If the barcode is known, we can"t assign it to the stock item! showSnackIcon( L10().barcodeInUse, icon: FontAwesomeIcons.qrcode, @@ -296,7 +296,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { L10().barcodeMissingHash, ); } else { - String hash = (data['hash'] ?? '') as String; + String hash = (data["hash"] ?? "") as String; if (hash.isNotEmpty) { item.update( @@ -343,10 +343,10 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler { @override Future onBarcodeMatched(BuildContext context, Map data) async { - // If the barcode points to a 'stocklocation', great! - if (data.containsKey('stocklocation')) { + // If the barcode points to a "stocklocation", great! + if (data.containsKey("stocklocation")) { // Extract location information - int location = (data['stocklocation']['pk'] ?? -1) as int; + int location = (data["stocklocation"]["pk"] ?? -1) as int; if (location == -1) { showSnackIcon( @@ -408,11 +408,11 @@ class StockLocationScanInItemsHandler extends BarcodeHandler { Future onBarcodeMatched(BuildContext context, Map data) async { // Returned barcode must match a stock item - if (data.containsKey('stockitem')) { + if (data.containsKey("stockitem")) { - int item_id = data['stockitem']['pk'] as int; + int item_id = data["stockitem"]["pk"] as int; - final InvenTreeStockItem? item = await InvenTreeStockItem().get(item_id) as InvenTreeStockItem; + final InvenTreeStockItem? item = await InvenTreeStockItem().get(item_id) as InvenTreeStockItem?; if (item == null) { @@ -468,7 +468,7 @@ class InvenTreeQRView extends StatefulWidget { final BarcodeHandler _handler; - InvenTreeQRView(this._handler, {Key? key}) : super(key: key); + const InvenTreeQRView(this._handler, {Key? key}) : super(key: key); @override State createState() => _QRViewState(_handler); @@ -477,7 +477,7 @@ class InvenTreeQRView extends StatefulWidget { class _QRViewState extends State { - final GlobalKey qrKey = GlobalKey(debugLabel: 'QR'); + final GlobalKey qrKey = GlobalKey(debugLabel: "QR"); QRViewController? _controller; diff --git a/lib/generated/i18n.dart b/lib/generated/i18n.dart index db983ef0..09177b85 100644 --- a/lib/generated/i18n.dart +++ b/lib/generated/i18n.dart @@ -12,11 +12,9 @@ import 'package:flutter/material.dart'; class S implements WidgetsLocalizations { const S(); - static const GeneratedLocalizationsDelegate delegate = - const GeneratedLocalizationsDelegate(); + static const GeneratedLocalizationsDelegate delegate = GeneratedLocalizationsDelegate(); - static S of(BuildContext context) => - Localizations.of(context, WidgetsLocalizations); + static S of(BuildContext context) => Localizations.of(context, WidgetsLocalizations); @override TextDirection get textDirection => TextDirection.ltr; diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 363c5d8b..332db292 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -1,6 +1,5 @@ -import 'package:inventree/api.dart'; - -import 'model.dart'; +import "package:inventree/api.dart"; +import "package:inventree/inventree/model.dart"; /* @@ -9,6 +8,8 @@ import 'model.dart'; class InvenTreeCompany extends InvenTreeModel { + InvenTreeCompany() : super(); + @override String get URL => "company/"; @@ -25,23 +26,21 @@ class InvenTreeCompany extends InvenTreeModel { }; } - InvenTreeCompany() : super(); + String get image => (jsondata["image"] ?? jsondata["thumbnail"] ?? InvenTreeAPI.staticImage) as String; - String get image => (jsondata['image'] ?? jsondata['thumbnail'] ?? InvenTreeAPI.staticImage) as String; + String get thumbnail => (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) as String; - String get thumbnail => (jsondata['thumbnail'] ?? jsondata['image'] ?? InvenTreeAPI.staticThumb) as String; + String get website => (jsondata["website"] ?? "") as String; - String get website => (jsondata['website'] ?? '') as String; + String get phone => (jsondata["phone"] ?? "") as String; - String get phone => (jsondata['phone'] ?? '') as String; + String get email => (jsondata["email"] ?? "") as String; - String get email => (jsondata['email'] ?? '') as String; + bool get isSupplier => (jsondata["is_supplier"] ?? false) as bool; - bool get isSupplier => (jsondata['is_supplier'] ?? false) as bool; + bool get isManufacturer => (jsondata["is_manufacturer"] ?? false) as bool; - bool get isManufacturer => (jsondata['is_manufacturer'] ?? false) as bool; - - bool get isCustomer => (jsondata['is_customer'] ?? false) as bool; + bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; InvenTreeCompany.fromJson(Map json) : super.fromJson(json); @@ -83,29 +82,29 @@ class InvenTreeSupplierPart extends InvenTreeModel { InvenTreeSupplierPart.fromJson(Map json) : super.fromJson(json); - int get manufacturerId => (jsondata['manufacturer'] ?? -1) as int; + int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; - String get manufacturerName => (jsondata['manufacturer_detail']['name'] ?? '') as String; + String get manufacturerName => (jsondata["manufacturer_detail"]["name"] ?? "") as String; - String get manufacturerImage => (jsondata['manufacturer_detail']['image'] ?? jsondata['manufacturer_detail']['thumbnail'] ?? InvenTreeAPI.staticThumb) as String; + String get manufacturerImage => (jsondata["manufacturer_detail"]["image"] ?? jsondata["manufacturer_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - int get manufacturerPartId => (jsondata['manufacturer_part'] ?? -1) as int; + int get manufacturerPartId => (jsondata["manufacturer_part"] ?? -1) as int; - int get supplierId => (jsondata['supplier'] ?? -1) as int; + int get supplierId => (jsondata["supplier"] ?? -1) as int; - String get supplierName => (jsondata['supplier_detail']['name'] ?? '') as String; + String get supplierName => (jsondata["supplier_detail"]["name"] ?? "") as String; - String get supplierImage => (jsondata['supplier_detail']['image'] ?? jsondata['supplier_detail']['thumbnail'] ?? InvenTreeAPI.staticThumb) as String; + String get supplierImage => (jsondata["supplier_detail"]["image"] ?? jsondata["supplier_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get SKU => (jsondata['SKU'] ?? '') as String; + String get SKU => (jsondata["SKU"] ?? "") as String; - String get MPN => (jsondata['MPN'] ?? '') as String; + String get MPN => (jsondata["MPN"] ?? "") as String; - int get partId => (jsondata['part'] ?? -1) as int; + int get partId => (jsondata["part"] ?? -1) as int; String get partImage => (jsondata["part_detail"]["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; - String get partName => (jsondata["part_detail"]["full_name"] ?? '') as String; + String get partName => (jsondata["part_detail"]["full_name"] ?? "") as String; @override InvenTreeModel createFromJson(Map json) { @@ -132,11 +131,11 @@ class InvenTreeManufacturerPart extends InvenTreeModel { InvenTreeManufacturerPart.fromJson(Map json) : super.fromJson(json); - int get partId => (jsondata['part'] ?? -1) as int; + int get partId => (jsondata["part"] ?? -1) as int; - int get manufacturerId => (jsondata['manufacturer'] ?? -1) as int; + int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; - String get MPN => (jsondata['MPN'] ?? '') as String; + String get MPN => (jsondata["MPN"] ?? "") as String; @override InvenTreeModel createFromJson(Map json) { diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 0b9abc02..1a5a1a03 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -1,18 +1,17 @@ -import 'dart:async'; -import 'dart:io'; +import "dart:async"; +import "dart:io"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/api.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:url_launcher/url_launcher.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/api.dart"; +import "package:flutter/cupertino.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/dialogs.dart"; +import "package:url_launcher/url_launcher.dart"; -import 'package:path/path.dart' as path; -import 'package:http/http.dart' as http; +import "package:path/path.dart" as path; -import '../l10.dart'; -import '../api_form.dart'; +import "package:inventree/l10.dart"; +import "package:inventree/api_form.dart"; // Paginated response object @@ -129,21 +128,21 @@ class InvenTreeModel { } - int get pk => (jsondata['pk'] ?? -1) as int; + int get pk => (jsondata["pk"] ?? -1) as int; // Some common accessors - String get name => (jsondata['name'] ?? '') as String; + String get name => (jsondata["name"] ?? "") as String; - String get description => (jsondata['description'] ?? '') as String; + String get description => (jsondata["description"] ?? "") as String; - String get notes => (jsondata['notes'] ?? '') as String; + String get notes => (jsondata["notes"] ?? "") as String; - int get parentId => (jsondata['parent'] ?? -1) as int; + int get parentId => (jsondata["parent"] ?? -1) as int; // Legacy API provided external link as "URL", while newer API uses "link" - String get link => (jsondata['link'] ?? jsondata['URL'] ?? '') as String; + String get link => (jsondata["link"] ?? jsondata["URL"] ?? "") as String; - void goToInvenTreePage() async { + Future goToInvenTreePage() async { if (await canLaunch(webUrl)) { await launch(webUrl); @@ -152,7 +151,7 @@ class InvenTreeModel { } } - void openLink() async { + Future openLink() async { if (link.isNotEmpty) { @@ -162,7 +161,7 @@ class InvenTreeModel { } } - String get keywords => (jsondata['keywords'] ?? '') as String; + String get keywords => (jsondata["keywords"] ?? "") as String; // Create a new object from JSON data (not a constructor!) InvenTreeModel createFromJson(Map json) { @@ -198,7 +197,7 @@ class InvenTreeModel { var response = await api.get(url, params: defaultGetFilters(), expectedStatusCode: 200); - if (!response.isValid() || response.data == null || !(response.data is Map)) { + if (!response.isValid() || response.data == null || (response.data is! Map)) { // Report error if (response.statusCode > 0) { @@ -267,12 +266,12 @@ class InvenTreeModel { // Override any default values for (String key in filters.keys) { - params[key] = filters[key] ?? ''; + params[key] = filters[key] ?? ""; } var response = await api.get(url, params: params); - if (!response.isValid() || response.data == null || !(response.data is Map)) { + if (!response.isValid() || response.data == null || response.data is! Map) { if (response.statusCode > 0) { await sentryReportMessage( @@ -302,18 +301,18 @@ class InvenTreeModel { Future create(Map data) async { - if (data.containsKey('pk')) { - data.remove('pk'); + if (data.containsKey("pk")) { + data.remove("pk"); } - if (data.containsKey('id')) { - data.remove('id'); + if (data.containsKey("id")) { + data.remove("id"); } var response = await api.post(URL, body: data); // Invalid response returned from server - if (!response.isValid() || response.data == null || !(response.data is Map)) { + if (!response.isValid() || response.data == null || response.data is! Map) { if (response.statusCode > 0) { await sentryReportMessage( @@ -345,7 +344,7 @@ class InvenTreeModel { var params = defaultListFilters(); for (String key in filters.keys) { - params[key] = filters[key] ?? ''; + params[key] = filters[key] ?? ""; } params["limit"] = "${limit}"; @@ -384,7 +383,7 @@ class InvenTreeModel { var params = defaultListFilters(); for (String key in filters.keys) { - params[key] = filters[key] ?? ''; + params[key] = filters[key] ?? ""; } var response = await api.get(URL, params: params); @@ -403,8 +402,8 @@ class InvenTreeModel { } else if (response.isMap()) { var mData = response.asMap(); - if (mData.containsKey('results')) { - data = (response.data['results'] ?? []) as List; + if (mData.containsKey("results")) { + data = (response.data["results"] ?? []) as List; } } @@ -423,9 +422,9 @@ class InvenTreeModel { // Provide a listing of objects at the endpoint // TODO - Static function which returns a list of objects (of this class) - // TODO - Define a 'delete' function + // TODO - Define a "delete" function - // TODO - Define a 'save' / 'update' function + // TODO - Define a "save" / "update" function // Override this function for each sub-class bool matchAgainstString(String filter) { @@ -462,7 +461,7 @@ class InvenTreeAttachment extends InvenTreeModel { InvenTreeAttachment() : super(); - String get attachment => (jsondata["attachment"] ?? '') as String; + String get attachment => (jsondata["attachment"] ?? "") as String; // Return the filename of the attachment String get filename { @@ -500,11 +499,11 @@ class InvenTreeAttachment extends InvenTreeModel { return FontAwesomeIcons.fileAlt; } - String get comment => (jsondata["comment"] ?? '') as String; + String get comment => (jsondata["comment"] ?? "") as String; DateTime? get uploadDate { if (jsondata.containsKey("upload_date")) { - return DateTime.tryParse((jsondata["upload_date"] ?? '') as String); + return DateTime.tryParse((jsondata["upload_date"] ?? "") as String); } else { return null; } @@ -517,8 +516,8 @@ class InvenTreeAttachment extends InvenTreeModel { final APIResponse response = await InvenTreeAPI().uploadFile( URL, attachment, - method: 'POST', - name: 'attachment', + method: "POST", + name: "attachment", fields: fields ); diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 237695ba..118a7c33 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -1,12 +1,12 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:inventree/l10.dart'; +import "dart:io"; -import 'model.dart'; -import 'dart:io'; -import 'package:http/http.dart' as http; +import "package:inventree/api.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/inventree/company.dart"; +import "package:flutter/cupertino.dart"; +import "package:inventree/l10.dart"; + +import "model.dart"; class InvenTreePartCategory extends InvenTreeModel { @@ -33,7 +33,7 @@ class InvenTreePartCategory extends InvenTreeModel { return filters; } - String get pathstring => (jsondata['pathstring'] ?? '') as String; + String get pathstring => (jsondata["pathstring"] ?? "") as String; String get parentpathstring { // TODO - Drive the refactor tractor through this @@ -52,7 +52,7 @@ class InvenTreePartCategory extends InvenTreeModel { return p; } - int get partcount => (jsondata['parts'] ?? 0) as int; + int get partcount => (jsondata["parts"] ?? 0) as int; InvenTreePartCategory() : super(); @@ -74,17 +74,15 @@ class InvenTreePartTestTemplate extends InvenTreeModel { @override String get URL => "part/test-template/"; - String get key => (jsondata['key'] ?? '') as String; + String get key => (jsondata["key"] ?? "") as String; - String get testName => (jsondata['test_name'] ?? '') as String; + String get testName => (jsondata["test_name"] ?? "") as String; - String get description => (jsondata['description'] ?? '') as String; + bool get required => (jsondata["required"] ?? false) as bool; - bool get required => (jsondata['required'] ?? false) as bool; + bool get requiresValue => (jsondata["requires_value"] ?? false) as bool; - bool get requiresValue => (jsondata['requires_value'] ?? false) as bool; - - bool get requiresAttachment => (jsondata['requires_attachment'] ?? false) as bool; + bool get requiresAttachment => (jsondata["requires_attachment"] ?? false) as bool; InvenTreePartTestTemplate() : super(); @@ -195,7 +193,7 @@ class InvenTreePart extends InvenTreeModel { }); } - int get supplierCount => (jsondata['suppliers'] ?? 0) as int; + int get supplierCount => (jsondata["suppliers"] ?? 0) as int; // Request supplier parts for this part Future> getSupplierParts() async { @@ -242,7 +240,7 @@ class InvenTreePart extends InvenTreeModel { } // Get the number of stock on order for this Part - double get onOrder => double.tryParse(jsondata['ordering'].toString()) ?? 0; + double get onOrder => double.tryParse(jsondata["ordering"].toString()) ?? 0; String get onOrderString { @@ -254,7 +252,7 @@ class InvenTreePart extends InvenTreeModel { } // Get the stock count for this Part - double get inStock => double.tryParse(jsondata['in_stock'].toString()) ?? 0; + double get inStock => double.tryParse(jsondata["in_stock"].toString()) ?? 0; String get inStockString { @@ -271,69 +269,69 @@ class InvenTreePart extends InvenTreeModel { return q; } - String get units => (jsondata["units"] as String) ?? ""; + String get units => (jsondata["units"] ?? "") as String; // Get the number of units being build for this Part - double get building => double.tryParse(jsondata['building'].toString()) ?? 0; + double get building => double.tryParse(jsondata["building"].toString()) ?? 0; // Get the number of BOM items in this Part (if it is an assembly) - int get bomItemCount => (jsondata['bom_items'] ?? 0) as int; + int get bomItemCount => (jsondata["bom_items"] ?? 0) as int; // Get the number of BOMs this Part is used in (if it is a component) - int get usedInCount => (jsondata['used_in'] ?? 0) as int; + int get usedInCount => (jsondata["used_in"] ?? 0) as int; - bool get isAssembly => (jsondata['assembly'] ?? false) as bool; + bool get isAssembly => (jsondata["assembly"] ?? false) as bool; - bool get isComponent => (jsondata['component'] ?? false) as bool; + bool get isComponent => (jsondata["component"] ?? false) as bool; - bool get isPurchaseable => (jsondata['purchaseable'] ?? false) as bool; + bool get isPurchaseable => (jsondata["purchaseable"] ?? false) as bool; - bool get isSalable => (jsondata['salable'] ?? false) as bool; + bool get isSalable => (jsondata["salable"] ?? false) as bool; - bool get isActive => (jsondata['active'] ?? false) as bool; + bool get isActive => (jsondata["active"] ?? false) as bool; - bool get isVirtual => (jsondata['virtual'] ?? false) as bool; + bool get isVirtual => (jsondata["virtual"] ?? false) as bool; - bool get isTrackable => (jsondata['trackable'] ?? false) as bool; + bool get isTrackable => (jsondata["trackable"] ?? false) as bool; // Get the IPN (internal part number) for the Part instance - String get IPN => (jsondata['IPN'] ?? '') as String; + String get IPN => (jsondata["IPN"] ?? "") as String; // Get the revision string for the Part instance - String get revision => (jsondata['revision'] ?? '') as String; + String get revision => (jsondata["revision"] ?? "") as String; - // Get the category ID for the Part instance (or 'null' if does not exist) - int get categoryId => (jsondata['category'] ?? -1) as int; + // Get the category ID for the Part instance (or "null" if does not exist) + int get categoryId => (jsondata["category"] ?? -1) as int; // Get the category name for the Part instance String get categoryName { // Inavlid category ID - if (categoryId <= 0) return ''; + if (categoryId <= 0) return ""; - if (!jsondata.containsKey('category_detail')) return ''; + if (!jsondata.containsKey("category_detail")) return ""; - return (jsondata['category_detail']?['name'] ?? '') as String; + return (jsondata["category_detail"]?["name"] ?? "") as String; } // Get the category description for the Part instance String get categoryDescription { // Invalid category ID - if (categoryId <= 0) return ''; + if (categoryId <= 0) return ""; - if (!jsondata.containsKey('category_detail')) return ''; + if (!jsondata.containsKey("category_detail")) return ""; - return (jsondata['category_detail']?['description'] ?? '') as String; + return (jsondata["category_detail"]?["description"] ?? "") as String; } // Get the image URL for the Part instance - String get _image => (jsondata['image'] ?? '') as String; + String get _image => (jsondata["image"] ?? "") as String; // Get the thumbnail URL for the Part instance - String get _thumbnail => (jsondata['thumbnail'] ?? '') as String; + String get _thumbnail => (jsondata["thumbnail"] ?? "") as String; // Return the fully-qualified name for the Part instance String get fullname { - String fn = (jsondata['full_name'] ?? '') as String; + String fn = (jsondata["full_name"] ?? "") as String; if (fn.isNotEmpty) return fn; @@ -369,15 +367,15 @@ class InvenTreePart extends InvenTreeModel { final APIResponse response = await InvenTreeAPI().uploadFile( url, image, - method: 'PATCH', - name: 'image', + method: "PATCH", + name: "image", ); return response.successful(); } // Return the "starred" status of this part - bool get starred => (jsondata['starred'] ?? false) as bool; + bool get starred => (jsondata["starred"] ?? false) as bool; InvenTreePart() : super(); diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index d5c02eef..b7698d36 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -1,7 +1,7 @@ -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/inventree/part.dart'; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/part.dart"; -import 'model.dart'; +import "package:inventree/inventree/model.dart"; // TODO: In the future, status codes should be retrieved from the server const int PO_STATUS_PENDING = 10; @@ -44,27 +44,27 @@ class InvenTreePurchaseOrder extends InvenTreeModel { }; } - String get issueDate => (jsondata['issue_date'] ?? '') as String; + String get issueDate => (jsondata["issue_date"] ?? "") as String; - String get completeDate => (jsondata['complete_date'] ?? '') as String; + String get completeDate => (jsondata["complete_date"] ?? "") as String; - String get creationDate => (jsondata['creation_date'] ?? '') as String; + String get creationDate => (jsondata["creation_date"] ?? "") as String; - String get targetDate => (jsondata['target_date'] ?? '') as String; + String get targetDate => (jsondata["target_date"] ?? "") as String; - int get lineItemCount => (jsondata['line_items'] ?? 0) as int; + int get lineItemCount => (jsondata["line_items"] ?? 0) as int; - bool get overdue => (jsondata['overdue'] ?? false) as bool; + bool get overdue => (jsondata["overdue"] ?? false) as bool; - String get reference => (jsondata['reference'] ?? '') as String; + String get reference => (jsondata["reference"] ?? "") as String; - int get responsibleId => (jsondata['responsible'] ?? -1) as int; + int get responsibleId => (jsondata["responsible"] ?? -1) as int; - int get supplierId => (jsondata['supplier'] ?? -1) as int; + int get supplierId => (jsondata["supplier"] ?? -1) as int; InvenTreeCompany? get supplier { - dynamic supplier_detail = jsondata["supplier_detail"] ?? null; + dynamic supplier_detail = jsondata["supplier_detail"]; if (supplier_detail == null) { return null; @@ -73,15 +73,15 @@ class InvenTreePurchaseOrder extends InvenTreeModel { } } - String get supplierReference => (jsondata['supplier_reference'] ?? '') as String; + String get supplierReference => (jsondata["supplier_reference"] ?? "") as String; - int get status => (jsondata['status'] ?? -1) as int; + int get status => (jsondata["status"] ?? -1) as int; - String get statusText => (jsondata['status_text'] ?? '') as String; + String get statusText => (jsondata["status_text"] ?? "") as String; - bool get isOpen => this.status == PO_STATUS_PENDING || this.status == PO_STATUS_PLACED; + bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; - bool get isFailed => this.status == PO_STATUS_CANCELLED || this.status == PO_STATUS_LOST || this.status == PO_STATUS_RETURNED; + bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; Future> getLineItems() async { @@ -146,17 +146,17 @@ class InvenTreePOLineItem extends InvenTreeModel { bool get isComplete => received >= quantity; - double get quantity => (jsondata['quantity'] ?? 0) as double; + double get quantity => (jsondata["quantity"] ?? 0) as double; - double get received => (jsondata['received'] ?? 0) as double; + double get received => (jsondata["received"] ?? 0) as double; double get outstanding => quantity - received; - String get reference => (jsondata['reference'] ?? '') as String; + String get reference => (jsondata["reference"] ?? "") as String; - int get orderId => (jsondata['order'] ?? -1) as int; + int get orderId => (jsondata["order"] ?? -1) as int; - int get supplierPartId => (jsondata['part'] ?? -1) as int; + int get supplierPartId => (jsondata["part"] ?? -1) as int; InvenTreePart? get part { dynamic part_detail = jsondata["part_detail"]; @@ -170,7 +170,7 @@ class InvenTreePOLineItem extends InvenTreeModel { InvenTreeSupplierPart? get supplierPart { - dynamic detail = jsondata["supplier_part_detail"] ?? null; + dynamic detail = jsondata["supplier_part_detail"]; if (detail == null) { return null; @@ -179,15 +179,15 @@ class InvenTreePOLineItem extends InvenTreeModel { } } - double get purchasePrice => double.parse((jsondata['purchase_price'] ?? '') as String); + double get purchasePrice => double.parse((jsondata["purchase_price"] ?? "") as String); - String get purchasePriceCurrency => (jsondata['purchase_price_currency'] ?? '') as String; + String get purchasePriceCurrency => (jsondata["purchase_price_currency"] ?? "") as String; - String get purchasePriceString => (jsondata['purchase_price_string'] ?? '') as String; + String get purchasePriceString => (jsondata["purchase_price_string"] ?? "") as String; - int get destination => (jsondata['destination'] ?? -1) as int; + int get destination => (jsondata["destination"] ?? -1) as int; - Map get destinationDetail => (jsondata['destination_detail'] ?? {}) as Map; + Map get destinationDetail => (jsondata["destination_detail"] ?? {}) as Map; InvenTreePOLineItem() : super(); diff --git a/lib/inventree/sentry.dart b/lib/inventree/sentry.dart index 50ae4ee4..46a412a7 100644 --- a/lib/inventree/sentry.dart +++ b/lib/inventree/sentry.dart @@ -1,10 +1,10 @@ -import 'dart:io'; +import "dart:io"; -import 'package:device_info_plus/device_info_plus.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; +import "package:device_info_plus/device_info_plus.dart"; +import "package:package_info_plus/package_info_plus.dart"; +import "package:sentry_flutter/sentry_flutter.dart"; -import 'package:inventree/api.dart'; +import "package:inventree/api.dart"; Future> getDeviceInfo() async { @@ -18,35 +18,35 @@ Future> getDeviceInfo() async { final iosDeviceInfo = await deviceInfo.iosInfo; device_info = { - 'name': iosDeviceInfo.name, - 'model': iosDeviceInfo.model, - 'systemName': iosDeviceInfo.systemName, - 'systemVersion': iosDeviceInfo.systemVersion, - 'localizedModel': iosDeviceInfo.localizedModel, - 'utsname': iosDeviceInfo.utsname.sysname, - 'identifierForVendor': iosDeviceInfo.identifierForVendor, - 'isPhysicalDevice': iosDeviceInfo.isPhysicalDevice, + "name": iosDeviceInfo.name, + "model": iosDeviceInfo.model, + "systemName": iosDeviceInfo.systemName, + "systemVersion": iosDeviceInfo.systemVersion, + "localizedModel": iosDeviceInfo.localizedModel, + "utsname": iosDeviceInfo.utsname.sysname, + "identifierForVendor": iosDeviceInfo.identifierForVendor, + "isPhysicalDevice": iosDeviceInfo.isPhysicalDevice, }; } else if (Platform.isAndroid) { final androidDeviceInfo = await deviceInfo.androidInfo; device_info = { - 'type': androidDeviceInfo.type, - 'model': androidDeviceInfo.model, - 'device': androidDeviceInfo.device, - 'id': androidDeviceInfo.id, - 'androidId': androidDeviceInfo.androidId, - 'brand': androidDeviceInfo.brand, - 'display': androidDeviceInfo.display, - 'hardware': androidDeviceInfo.hardware, - 'manufacturer': androidDeviceInfo.manufacturer, - 'product': androidDeviceInfo.product, - 'version': androidDeviceInfo.version.release, - 'supported32BitAbis': androidDeviceInfo.supported32BitAbis, - 'supported64BitAbis': androidDeviceInfo.supported64BitAbis, - 'supportedAbis': androidDeviceInfo.supportedAbis, - 'isPhysicalDevice': androidDeviceInfo.isPhysicalDevice, + "type": androidDeviceInfo.type, + "model": androidDeviceInfo.model, + "device": androidDeviceInfo.device, + "id": androidDeviceInfo.id, + "androidId": androidDeviceInfo.androidId, + "brand": androidDeviceInfo.brand, + "display": androidDeviceInfo.display, + "hardware": androidDeviceInfo.hardware, + "manufacturer": androidDeviceInfo.manufacturer, + "product": androidDeviceInfo.product, + "version": androidDeviceInfo.version.release, + "supported32BitAbis": androidDeviceInfo.supported32BitAbis, + "supported64BitAbis": androidDeviceInfo.supported64BitAbis, + "supportedAbis": androidDeviceInfo.supportedAbis, + "isPhysicalDevice": androidDeviceInfo.isPhysicalDevice, }; } @@ -90,7 +90,7 @@ Future sentryReportMessage(String message, {Map? context}) if (isInDebugMode()) { - print('----- In dev mode. Not sending message to Sentry.io -----'); + print("----- In dev mode. Not sending message to Sentry.io -----"); return true; } @@ -117,7 +117,7 @@ Future sentryReportMessage(String message, {Map? context}) Future sentryReportError(dynamic error, dynamic stackTrace) async { - print('----- Sentry Intercepted error: $error -----'); + print("----- Sentry Intercepted error: $error -----"); print(stackTrace); // Errors thrown in development mode are unlikely to be interesting. You can @@ -125,7 +125,7 @@ Future sentryReportError(dynamic error, dynamic stackTrace) async { // the report. if (isInDebugMode()) { - print('----- In dev mode. Not sending report to Sentry.io -----'); + print("----- In dev mode. Not sending report to Sentry.io -----"); return; } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 56b54d73..87f97df2 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,15 +1,13 @@ -import 'package:intl/intl.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:http/http.dart' as http; -import 'model.dart'; -import 'package:inventree/l10.dart'; +import "dart:async"; +import "package:intl/intl.dart"; +import "package:inventree/inventree/part.dart"; +import "package:flutter/cupertino.dart"; -import 'dart:async'; -import 'dart:io'; +import "package:inventree/inventree/model.dart"; +import "package:inventree/l10.dart"; -import 'package:inventree/api.dart'; +import "package:inventree/api.dart"; class InvenTreeStockItemTestResult extends InvenTreeModel { @@ -31,19 +29,17 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { }; } - String get key => (jsondata['key'] ?? '') as String; + String get key => (jsondata["key"] ?? "") as String; - String get testName => (jsondata['test'] ?? '') as String; + String get testName => (jsondata["test"] ?? "") as String; - bool get result => (jsondata['result'] ?? false) as bool; + bool get result => (jsondata["result"] ?? false) as bool; - String get value => (jsondata['value'] ?? '') as String; + String get value => (jsondata["value"] ?? "") as String; - String get notes => (jsondata['notes'] ?? '') as String; + String get attachment => (jsondata["attachment"] ?? "") as String; - String get attachment => (jsondata['attachment'] ?? '') as String; - - String get date => (jsondata['date'] ?? '') as String; + String get date => (jsondata["date"] ?? "") as String; InvenTreeStockItemTestResult() : super(); @@ -204,17 +200,17 @@ class InvenTreeStockItem extends InvenTreeModel { }); } - String get uid => (jsondata['uid'] ?? '') as String; + String get uid => (jsondata["uid"] ?? "") as String; - int get status => (jsondata['status'] ?? -1) as int; + int get status => (jsondata["status"] ?? -1) as int; - String get packaging => (jsondata["packaging"] ?? '') as String; + String get packaging => (jsondata["packaging"] ?? "") as String; - String get batch => (jsondata["batch"] ?? '') as String; + String get batch => (jsondata["batch"] ?? "") as String; - int get partId => (jsondata['part'] ?? -1) as int; + int get partId => (jsondata["part"] ?? -1) as int; - String get purchasePrice => (jsondata['purchase_price'] ?? '') as String; + String get purchasePrice => (jsondata["purchase_price"] ?? "") as String; bool get hasPurchasePrice { @@ -223,14 +219,14 @@ class InvenTreeStockItem extends InvenTreeModel { return pp.isNotEmpty && pp.trim() != "-"; } - int get purchaseOrderId => (jsondata['purchase_order'] ?? -1) as int; + int get purchaseOrderId => (jsondata["purchase_order"] ?? -1) as int; - int get trackingItemCount => (jsondata['tracking_items'] ?? 0) as int; + int get trackingItemCount => (jsondata["tracking_items"] ?? 0) as int; // Date of last update DateTime? get updatedDate { if (jsondata.containsKey("updated")) { - return DateTime.tryParse((jsondata["updated"] ?? '') as String); + return DateTime.tryParse((jsondata["updated"] ?? "") as String); } else { return null; } @@ -250,7 +246,7 @@ class InvenTreeStockItem extends InvenTreeModel { DateTime? get stocktakeDate { if (jsondata.containsKey("stocktake_date")) { - return DateTime.tryParse((jsondata["stocktake_date"] ?? '') as String); + return DateTime.tryParse((jsondata["stocktake_date"] ?? "") as String); } else { return null; } @@ -270,45 +266,45 @@ class InvenTreeStockItem extends InvenTreeModel { String get partName { - String nm = ''; + String nm = ""; // Use the detailed part information as priority - if (jsondata.containsKey('part_detail')) { - nm = (jsondata['part_detail']['full_name'] ?? '') as String; + if (jsondata.containsKey("part_detail")) { + nm = (jsondata["part_detail"]["full_name"] ?? "") as String; } // Backup if first value fails if (nm.isEmpty) { - nm = (jsondata['part__name'] ?? '') as String; + nm = (jsondata["part__name"] ?? "") as String; } return nm; } String get partDescription { - String desc = ''; + String desc = ""; // Use the detailed part description as priority - if (jsondata.containsKey('part_detail')) { - desc = (jsondata['part_detail']['description'] ?? '') as String; + if (jsondata.containsKey("part_detail")) { + desc = (jsondata["part_detail"]["description"] ?? "") as String; } if (desc.isEmpty) { - desc = (jsondata['part__description'] ?? '') as String; + desc = (jsondata["part__description"] ?? "") as String; } return desc; } String get partImage { - String img = ''; + String img = ""; - if (jsondata.containsKey('part_detail')) { - img = (jsondata['part_detail']['thumbnail'] ?? '') as String; + if (jsondata.containsKey("part_detail")) { + img = (jsondata["part_detail"]["thumbnail"] ?? "") as String; } if (img.isEmpty) { - img = (jsondata['part__thumbnail'] ?? '') as String; + img = (jsondata["part__thumbnail"] ?? "") as String; } return img; @@ -321,63 +317,63 @@ class InvenTreeStockItem extends InvenTreeModel { String thumb = ""; - thumb = (jsondata['part_detail']?['thumbnail'] ?? '') as String; + thumb = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; - // Use 'image' as a backup + // Use "image" as a backup if (thumb.isEmpty) { - thumb = (jsondata['part_detail']?['image'] ?? '') as String; + thumb = (jsondata["part_detail"]?["image"] ?? "") as String; } // Try a different approach if (thumb.isEmpty) { - thumb = (jsondata['part__thumbnail'] ?? '') as String; + thumb = (jsondata["part__thumbnail"] ?? "") as String; } - // Still no thumbnail? Use the 'no image' image + // Still no thumbnail? Use the "no image" image if (thumb.isEmpty) thumb = InvenTreeAPI.staticThumb; return thumb; } - int get supplierPartId => (jsondata['supplier_part'] ?? -1) as int; + int get supplierPartId => (jsondata["supplier_part"] ?? -1) as int; String get supplierImage { - String thumb = ''; + String thumb = ""; if (jsondata.containsKey("supplier_detail")) { - thumb = (jsondata['supplier_detail']['supplier_logo'] ?? '') as String; + thumb = (jsondata["supplier_detail"]["supplier_logo"] ?? "") as String; } return thumb; } String get supplierName { - String sname = ''; + String sname = ""; if (jsondata.containsKey("supplier_detail")) { - sname = (jsondata["supplier_detail"]["supplier_name"] ?? '') as String; + sname = (jsondata["supplier_detail"]["supplier_name"] ?? "") as String; } return sname; } String get units { - return (jsondata['part_detail']?['units'] ?? '') as String; + return (jsondata["part_detail"]?["units"] ?? "") as String; } String get supplierSKU { - String sku = ''; + String sku = ""; if (jsondata.containsKey("supplier_detail")) { - sku = (jsondata["supplier_detail"]["SKU"] ?? '') as String; + sku = (jsondata["supplier_detail"]["SKU"] ?? "") as String; } return sku; } - String get serialNumber => (jsondata['serial'] ?? '') as String; + String get serialNumber => (jsondata["serial"] ?? "") as String; - double get quantity => double.tryParse(jsondata['quantity'].toString()) ?? 0; + double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0; String get quantityString { @@ -395,33 +391,33 @@ class InvenTreeStockItem extends InvenTreeModel { return q; } - int get locationId => (jsondata['location'] ?? -1) as int; + int get locationId => (jsondata["location"] ?? -1) as int; bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1; String serialOrQuantityDisplay() { if (isSerialized()) { - return 'SN ${serialNumber}'; + return "SN ${serialNumber}"; } // Is an integer? if (quantity.toInt() == quantity) { - return '${quantity.toInt()}'; + return "${quantity.toInt()}"; } - return '${quantity}'; + return "${quantity}"; } String get locationName { - String loc = ''; + String loc = ""; - if (locationId == -1 || !jsondata.containsKey('location_detail')) return 'Unknown Location'; + if (locationId == -1 || !jsondata.containsKey("location_detail")) return "Unknown Location"; - loc = (jsondata['location_detail']['name'] ?? '') as String; + loc = (jsondata["location_detail"]["name"] ?? "") as String; // Old-style name if (loc.isEmpty) { - loc = (jsondata['location__name'] ?? '') as String; + loc = (jsondata["location__name"] ?? "") as String; } return loc; @@ -429,9 +425,9 @@ class InvenTreeStockItem extends InvenTreeModel { String get locationPathString { - if (locationId == -1 || !jsondata.containsKey('location_detail')) return L10().locationNotSet; + if (locationId == -1 || !jsondata.containsKey("location_detail")) return L10().locationNotSet; - String _loc = (jsondata['location_detail']['pathstring'] ?? '') as String; + String _loc = (jsondata["location_detail"]["pathstring"] ?? "") as String; if (_loc.isNotEmpty) { return _loc; @@ -483,7 +479,7 @@ class InvenTreeStockItem extends InvenTreeModel { "pk": "${pk}", "quantity": "${q}", }, - "notes": notes ?? '', + "notes": notes ?? "", }, expectedStatusCode: 200 ); @@ -540,7 +536,7 @@ class InvenTreeStockLocation extends InvenTreeModel { @override String get URL => "stock/location/"; - String get pathstring => (jsondata['pathstring'] ?? '') as String; + String get pathstring => (jsondata["pathstring"] ?? "") as String; @override Map formFields() { @@ -553,13 +549,13 @@ class InvenTreeStockLocation extends InvenTreeModel { String get parentpathstring { // TODO - Drive the refactor tractor through this - List psplit = pathstring.split('/'); + List psplit = pathstring.split("/"); if (psplit.length > 0) { psplit.removeLast(); } - String p = psplit.join('/'); + String p = psplit.join("/"); if (p.isEmpty) { p = "Top level stock location"; @@ -568,7 +564,7 @@ class InvenTreeStockLocation extends InvenTreeModel { return p; } - int get itemcount => (jsondata['items'] ?? 0) as int; + int get itemcount => (jsondata["items"] ?? 0) as int; InvenTreeStockLocation() : super(); diff --git a/lib/l10.dart b/lib/l10.dart index 04b70f40..ce9f3199 100644 --- a/lib/l10.dart +++ b/lib/l10.dart @@ -1,8 +1,8 @@ -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations_en.dart'; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; +import "package:flutter_gen/gen_l10n/app_localizations_en.dart"; -import 'package:one_context/one_context.dart'; -import 'package:flutter/material.dart'; +import "package:one_context/one_context.dart"; +import "package:flutter/material.dart"; // Shortcut function to reduce boilerplate! I18N L10() diff --git a/lib/main.dart b/lib/main.dart index 40c7176a..50561cbe 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,19 +1,18 @@ -import 'dart:async'; +import "dart:async"; -import 'package:inventree/inventree/sentry.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import "package:flutter_localizations/flutter_localizations.dart"; +import "package:flutter_gen/gen_l10n/app_localizations.dart"; -import 'package:inventree/widget/home.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:one_context/one_context.dart'; -import 'package:package_info_plus/package_info_plus.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:one_context/one_context.dart"; +import "package:package_info_plus/package_info_plus.dart"; +import "package:flutter/foundation.dart"; +import "package:sentry_flutter/sentry_flutter.dart"; -import 'dsn.dart'; - -import 'package:flutter/foundation.dart'; -import 'package:sentry_flutter/sentry_flutter.dart'; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/dsn.dart"; +import "package:inventree/widget/home.dart"; Future main() async { @@ -75,24 +74,24 @@ class InvenTreeApp extends StatelessWidget { GlobalCupertinoLocalizations.delegate, ], supportedLocales: [ - const Locale('de', ''), // German - const Locale('el', ''), // Greek - const Locale('en', ''), // English - const Locale('es', ''), // Spanish - const Locale('fr', ''), // French - const Locale('he', ''), // Hebrew - const Locale('it', ''), // Italian - const Locale('ja', ''), // Japanese - const Locale('ko', ''), // Korean - const Locale('nl', ''), // Dutch - const Locale('no', ''), // Norwegian - const Locale('pl', ''), // Polish - const Locale('ru', ''), // Russian - const Locale('sv', ''), // Swedish - const Locale('th', ''), // Thai - const Locale('tr', ''), // Turkish - const Locale('vi', ''), // Vietnamese - const Locale('zh-CN', ''), // Chinese + const Locale("de", ""), // German + const Locale("el", ""), // Greek + const Locale("en", ""), // English + const Locale("es", ""), // Spanish + const Locale("fr", ""), // French + const Locale("he", ""), // Hebrew + const Locale("it", ""), // Italian + const Locale("ja", ""), // Japanese + const Locale("ko", ""), // Korean + const Locale("nl", ""), // Dutch + const Locale("no", ""), // Norwegian + const Locale("pl", ""), // Polish + const Locale("ru", ""), // Russian + const Locale("sv", ""), // Swedish + const Locale("th", ""), // Thai + const Locale("tr", ""), // Turkish + const Locale("vi", ""), // Vietnamese + const Locale("zh-CN", ""), // Chinese ], ); diff --git a/lib/preferences.dart b/lib/preferences.dart index 645f614d..f35f7ab6 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -1,8 +1,10 @@ -import 'package:path_provider/path_provider.dart'; -import 'package:sembast/sembast.dart'; -import 'package:sembast/sembast_io.dart'; -import 'package:path/path.dart'; -import 'dart:async'; +import "dart:async"; + +import "package:path_provider/path_provider.dart"; +import "package:sembast/sembast.dart"; +import "package:sembast/sembast_io.dart"; +import "package:path/path.dart"; + /* * Class for storing InvenTree preferences in a NoSql DB @@ -43,7 +45,7 @@ class InvenTreePreferencesDB { print("Path: ${appDocumentDir.path}"); // Path with the form: /platform-specific-directory/demo.db - final dbPath = join(appDocumentDir.path, 'InvenTreeSettings.db'); + final dbPath = join(appDocumentDir.path, "InvenTreeSettings.db"); final database = await databaseFactoryIo.openDatabase(dbPath); @@ -55,7 +57,7 @@ class InvenTreePreferencesDB { class InvenTreePreferences { /* The following settings are not stored to persistent storage, - * instead they are only used as 'session preferences'. + * instead they are only used as "session preferences". * They are kept here as a convenience only. */ diff --git a/lib/settings/about.dart b/lib/settings/about.dart index de208666..87ba5c2f 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -1,22 +1,22 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/settings/release.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/settings/release.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:package_info_plus/package_info_plus.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:flutter/services.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:package_info_plus/package_info_plus.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; class InvenTreeAboutWidget extends StatelessWidget { final PackageInfo info; - InvenTreeAboutWidget(this.info) : super(); + const InvenTreeAboutWidget(this.info) : super(); - void _releaseNotes(BuildContext context) async { + Future _releaseNotes(BuildContext context) async { // Load release notes from external file String notes = await rootBundle.loadString("assets/release_notes.md"); @@ -27,7 +27,7 @@ class InvenTreeAboutWidget extends StatelessWidget { ); } - void _credits(BuildContext context) async { + Future _credits(BuildContext context) async { String notes = await rootBundle.loadString("assets/credits.md"); diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index bf175452..4cee024a 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -1,12 +1,12 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; +import "package:flutter/material.dart"; +import "package:flutter/cupertino.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import 'package:inventree/app_settings.dart'; +import "package:inventree/app_settings.dart"; class InvenTreeAppSettingsWidget extends StatefulWidget { @override @@ -31,7 +31,7 @@ class _InvenTreeAppSettingsState extends State { loadSettings(); } - void loadSettings() async { + Future loadSettings() async { barcodeSounds = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; serverSounds = await InvenTreeSettingsManager().getValue("serverSounds", true) as bool; @@ -42,7 +42,7 @@ class _InvenTreeAppSettingsState extends State { }); } - void setBarcodeSounds(bool en) async { + Future setBarcodeSounds(bool en) async { await InvenTreeSettingsManager().setValue("barcodeSounds", en); barcodeSounds = await InvenTreeSettingsManager().getBool("barcodeSounds", true); @@ -51,7 +51,7 @@ class _InvenTreeAppSettingsState extends State { }); } - void setServerSounds(bool en) async { + Future setServerSounds(bool en) async { await InvenTreeSettingsManager().setValue("serverSounds", en); serverSounds = await InvenTreeSettingsManager().getBool("serverSounds", true); @@ -60,7 +60,7 @@ class _InvenTreeAppSettingsState extends State { }); } - void setPartSubcategory(bool en) async { + Future setPartSubcategory(bool en) async { await InvenTreeSettingsManager().setValue("partSubcategory", en); partSubcategory = await InvenTreeSettingsManager().getBool("partSubcategory", true); @@ -68,7 +68,7 @@ class _InvenTreeAppSettingsState extends State { }); } - void setStockSublocation(bool en) async { + Future setStockSublocation(bool en) async { await InvenTreeSettingsManager().setValue("stockSublocation", en); stockSublocation = await InvenTreeSettingsManager().getBool("stockSublocation", true); diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 7edf6fa0..59487df4 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -1,15 +1,12 @@ -import 'package:inventree/app_colors.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/widget/spinner.dart'; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; - -import 'package:inventree/l10.dart'; - -import '../api.dart'; -import '../user_profile.dart'; +import "package:inventree/app_colors.dart"; +import "package:inventree/widget/dialogs.dart"; +import "package:inventree/widget/spinner.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/api.dart"; +import "package:inventree/user_profile.dart"; class InvenTreeLoginSettingsWidget extends StatefulWidget { @@ -22,15 +19,13 @@ class _InvenTreeLoginSettingsState extends State { final GlobalKey<_InvenTreeLoginSettingsState> _loginKey = GlobalKey<_InvenTreeLoginSettingsState>(); - final GlobalKey _addProfileKey = new GlobalKey(); - List profiles = []; _InvenTreeLoginSettingsState() { _reload(); } - void _reload() async { + Future _reload() async { profiles = await UserProfileDBManager().getAllProfiles(); @@ -50,7 +45,7 @@ class _InvenTreeLoginSettingsState extends State { }); } - void _selectProfile(BuildContext context, UserProfile profile) async { + Future _selectProfile(BuildContext context, UserProfile profile) async { // Disconnect InvenTree InvenTreeAPI().disconnectFromServer(); @@ -73,18 +68,18 @@ class _InvenTreeLoginSettingsState extends State { _reload(); } - void _deleteProfile(UserProfile profile) async { + Future _deleteProfile(UserProfile profile) async { await UserProfileDBManager().deleteProfile(profile); _reload(); - if (InvenTreeAPI().isConnected() && profile.key == (InvenTreeAPI().profile?.key ?? '')) { + if (InvenTreeAPI().isConnected() && profile.key == (InvenTreeAPI().profile?.key ?? "")) { InvenTreeAPI().disconnectFromServer(); } } - void _updateProfile(UserProfile? profile) async { + Future _updateProfile(UserProfile? profile) async { if (profile == null) { return; @@ -92,7 +87,7 @@ class _InvenTreeLoginSettingsState extends State { _reload(); - if (InvenTreeAPI().isConnected() && InvenTreeAPI().profile != null && profile.key == (InvenTreeAPI().profile?.key ?? '')) { + if (InvenTreeAPI().isConnected() && InvenTreeAPI().profile != null && profile.key == (InvenTreeAPI().profile?.key ?? "")) { // Attempt server login (this will load the newly selected profile InvenTreeAPI().connectToServer().then((result) { @@ -108,7 +103,7 @@ class _InvenTreeLoginSettingsState extends State { if (!profile.selected) return null; // Selected, but (for some reason) not the same as the API... - if ((InvenTreeAPI().profile?.key ?? '') != profile.key) { + if ((InvenTreeAPI().profile?.key ?? "") != profile.key) { return FaIcon( FontAwesomeIcons.questionCircle, color: COLOR_WARNING diff --git a/lib/settings/release.dart b/lib/settings/release.dart index aa456d7c..b2589a85 100644 --- a/lib/settings/release.dart +++ b/lib/settings/release.dart @@ -1,14 +1,14 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:inventree/l10.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:flutter_markdown/flutter_markdown.dart"; +import "package:inventree/l10.dart"; class ReleaseNotesWidget extends StatelessWidget { final String releaseNotes; - ReleaseNotesWidget(this.releaseNotes); + const ReleaseNotesWidget(this.releaseNotes); @override Widget build (BuildContext context) { @@ -29,7 +29,7 @@ class CreditsWidget extends StatelessWidget { final String credits; - CreditsWidget(this.credits); + const CreditsWidget(this.credits); @override Widget build (BuildContext context) { diff --git a/lib/settings/settings.dart b/lib/settings/settings.dart index b8bb4c37..946e94f8 100644 --- a/lib/settings/settings.dart +++ b/lib/settings/settings.dart @@ -1,18 +1,16 @@ -import 'package:inventree/app_colors.dart'; -import 'package:inventree/settings/about.dart'; -import 'package:inventree/settings/app_settings.dart'; -import 'package:inventree/settings/login.dart'; +import "package:inventree/app_colors.dart"; +import "package:inventree/settings/about.dart"; +import "package:inventree/settings/app_settings.dart"; +import "package:inventree/settings/login.dart"; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/l10.dart'; -import 'package:inventree/widget/submit_feedback.dart'; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/submit_feedback.dart"; -import 'package:url_launcher/url_launcher.dart'; +import "package:url_launcher/url_launcher.dart"; -import 'login.dart'; - -import 'package:package_info_plus/package_info_plus.dart'; +import "package:package_info_plus/package_info_plus.dart"; class InvenTreeSettingsWidget extends StatefulWidget { // InvenTree settings view @@ -95,30 +93,30 @@ class _InvenTreeSettingsState extends State { } - void _openDocs() async { + Future _openDocs() async { if (await canLaunch(docsUrl)) { await launch(docsUrl); } } - void _translate() async { - final String url = "https://crowdin.com/project/inventree"; + Future _translate() async { + const String url = "https://crowdin.com/project/inventree"; if (await canLaunch(url)) { await launch(url); } } - void _editServerSettings() async { + Future _editServerSettings() async { Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeLoginSettingsWidget())); } - void _editAppSettings() async { + Future _editAppSettings() async { Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeAppSettingsWidget())); } - void _about() async { + Future _about() async { PackageInfo.fromPlatform().then((PackageInfo info) { Navigator.push(context, @@ -126,7 +124,7 @@ class _InvenTreeSettingsState extends State { }); } - void _submitFeedback(BuildContext context) async { + Future _submitFeedback(BuildContext context) async { Navigator.push( context, diff --git a/lib/user_profile.dart b/lib/user_profile.dart index b0efd89a..26e055b6 100644 --- a/lib/user_profile.dart +++ b/lib/user_profile.dart @@ -2,8 +2,8 @@ /* * Class for InvenTree user / login details */ -import 'package:sembast/sembast.dart'; -import 'preferences.dart'; +import "package:sembast/sembast.dart"; +import "preferences.dart"; class UserProfile { @@ -38,10 +38,10 @@ class UserProfile { factory UserProfile.fromJson(int key, Map json, bool isSelected) => UserProfile( key: key, - name: json['name'] as String, - server: json['server'] as String, - username: json['username'] as String, - password: json['password'] as String, + name: json["name"] as String, + server: json["server"] as String, + username: json["username"] as String, + password: json["password"] as String, selected: isSelected, ); @@ -70,7 +70,7 @@ class UserProfileDBManager { final profiles = await store.find(await _db, finder: finder); - return profiles.length > 0; + return profiles.isNotEmpty; } Future addProfile(UserProfile profile) async { diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index b0faa09a..f3d60034 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -1,27 +1,27 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/app_settings.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/progress.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/progress.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/paginator.dart'; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/paginator.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/foundation.dart"; +import "package:flutter/material.dart"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; class CategoryDisplayWidget extends StatefulWidget { - CategoryDisplayWidget(this.category, {Key? key}) : super(key: key); + const CategoryDisplayWidget(this.category, {Key? key}) : super(key: key); final InvenTreePartCategory? category; @@ -41,7 +41,7 @@ class _CategoryDisplayState extends RefreshableState { List actions = []; - if ((category != null) && InvenTreeAPI().checkPermission('part_category', 'change')) { + if ((category != null) && InvenTreeAPI().checkPermission("part_category", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), @@ -278,7 +278,7 @@ class _CategoryDisplayState extends RefreshableState { getCategoryDescriptionCard(extra: false), ]; - if (InvenTreeAPI().checkPermission('part', 'add')) { + if (InvenTreeAPI().checkPermission("part", "add")) { tiles.add( ListTile( title: Text(L10().categoryCreate), @@ -331,7 +331,9 @@ class _CategoryDisplayState extends RefreshableState { ); case 1: return PaginatedPartList( - {"category": "${category?.pk ?? null}"}, + { + "category": "${category?.pk ?? 'null'}" + }, ); case 2: return ListView( @@ -350,7 +352,7 @@ class _CategoryDisplayState extends RefreshableState { class SubcategoryList extends StatelessWidget { final List _categories; - SubcategoryList(this._categories); + const SubcategoryList(this._categories); void _openCategory(BuildContext context, int pk) { diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index 86d4c5e6..4bea741d 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -1,18 +1,18 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; class CompanyDetailWidget extends StatefulWidget { final InvenTreeCompany company; - CompanyDetailWidget(this.company, {Key? key}) : super(key: key); + const CompanyDetailWidget(this.company, {Key? key}) : super(key: key); @override _CompanyDetailState createState() => _CompanyDetailState(company); @@ -62,7 +62,7 @@ class _CompanyDetailState extends RefreshableState { await company.reload(); } - void editCompany(BuildContext context) async { + Future editCompany(BuildContext context) async { company.editForm( context, diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index 15a4af98..acf9b3f8 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -1,16 +1,16 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; -import 'package:inventree/api.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/company_detail.dart'; +import "package:inventree/api.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/company_detail.dart"; -import '../l10.dart'; +import "package:inventree/l10.dart"; class CompanyListWidget extends StatefulWidget { diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index 641a55c9..2d80d650 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -1,12 +1,12 @@ -import 'package:inventree/app_settings.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:audioplayers/audioplayers.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/l10.dart'; -import 'package:one_context/one_context.dart'; +import "package:inventree/app_settings.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:audioplayers/audioplayers.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; +import "package:one_context/one_context.dart"; Future confirmationDialog(String title, String text, {String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async { diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 9fa9bde8..475ed27c 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -1,21 +1,21 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/barcode.dart'; -import 'package:inventree/widget/company_list.dart'; -import 'package:inventree/widget/search.dart'; -import 'package:flutter/material.dart'; -import 'package:inventree/l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/barcode.dart"; +import "package:inventree/widget/company_list.dart"; +import "package:inventree/widget/search.dart"; +import "package:flutter/material.dart"; +import "package:inventree/l10.dart"; -import 'package:inventree/widget/category_display.dart'; -import 'package:inventree/widget/location_display.dart'; +import "package:inventree/widget/category_display.dart"; +import "package:inventree/widget/location_display.dart"; -import 'package:inventree/settings/settings.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import "package:inventree/settings/settings.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; class InvenTreeDrawer extends StatelessWidget { final BuildContext context; - InvenTreeDrawer(this.context); + const InvenTreeDrawer(this.context); void _closeDrawer() { // Close the drawer @@ -50,7 +50,7 @@ class InvenTreeDrawer extends StatelessWidget { * Launch the camera to scan a QR code. * Upon successful scan, data are passed off to be decoded. */ - void _scan() async { + Future _scan() async { if (!InvenTreeAPI().checkConnection(context)) return; _closeDrawer(); diff --git a/lib/widget/fields.dart b/lib/widget/fields.dart index f8016075..7c6a74d6 100644 --- a/lib/widget/fields.dart +++ b/lib/widget/fields.dart @@ -1,15 +1,13 @@ +import "dart:async"; +import "dart:io"; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:inventree/l10.dart'; - -import 'dart:async'; -import 'dart:io'; - -import 'package:one_context/one_context.dart'; +import "package:file_picker/file_picker.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:image_picker/image_picker.dart"; +import "package:one_context/one_context.dart"; +import "package:inventree/l10.dart"; class FilePickerDialog { @@ -167,7 +165,7 @@ class CheckBoxField extends FormField { class StringField extends TextFormField { - StringField({String label = "", String? hint, String? initial, Function(String?)? onSaved, Function? validator, bool allowEmpty = false, bool isEnabled = true}) : + StringField({String label = "", String? hint, String? initial, Function(String?)? onSaved, Function(String?)? validator, bool allowEmpty = false, bool isEnabled = true}) : super( decoration: InputDecoration( labelText: allowEmpty ? label : label + "*", @@ -182,7 +180,7 @@ class StringField extends TextFormField { } if (validator != null) { - return validator(value); + return validator(value) as String?; } return null; diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 77f59ef5..6589a89d 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -1,29 +1,29 @@ -import 'package:inventree/app_colors.dart'; -import 'package:inventree/user_profile.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import "package:inventree/app_colors.dart"; +import "package:inventree/user_profile.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import 'package:inventree/barcode.dart'; -import 'package:inventree/api.dart'; +import "package:inventree/barcode.dart"; +import "package:inventree/api.dart"; -import 'package:inventree/settings/login.dart'; +import "package:inventree/settings/login.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/purchase_order_list.dart'; -import 'package:inventree/widget/search.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:inventree/widget/spinner.dart'; -import 'package:inventree/widget/drawer.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/purchase_order_list.dart"; +import "package:inventree/widget/search.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/spinner.dart"; +import "package:inventree/widget/drawer.dart"; class InvenTreeHomePage extends StatefulWidget { - InvenTreeHomePage({Key? key}) : super(key: key); + const InvenTreeHomePage({Key? key}) : super(key: key); @override _InvenTreeHomePageState createState() => _InvenTreeHomePageState(); @@ -125,7 +125,7 @@ class _InvenTreeHomePageState extends State { }); } - void _loadProfile() async { + Future _loadProfile() async { _profile = await UserProfileDBManager().getSelectedProfile(); diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 16d9a9a8..31ab8c4d 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -1,21 +1,21 @@ -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/app_settings.dart'; -import 'package:inventree/barcode.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/widget/progress.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/barcode.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/progress.dart"; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/stock_detail.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/l10.dart'; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/stock_detail.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/l10.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/foundation.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:flutter/foundation.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; class LocationDisplayWidget extends StatefulWidget { @@ -62,7 +62,7 @@ class _LocationDisplayState extends RefreshableState { ); */ - if ((location != null) && (InvenTreeAPI().checkPermission('stock_location', 'change'))) { + if ((location != null) && (InvenTreeAPI().checkPermission("stock_location", "change"))) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), @@ -96,7 +96,7 @@ class _LocationDisplayState extends RefreshableState { List _sublocations = []; - String _locationFilter = ''; + String _locationFilter = ""; List get sublocations { @@ -340,7 +340,7 @@ List detailTiles() { tiles.add(locationDescriptionCard(includeActions: false)); - if (InvenTreeAPI().checkPermission('stock', 'add')) { + if (InvenTreeAPI().checkPermission("stock", "add")) { tiles.add( ListTile( @@ -368,7 +368,7 @@ List detailTiles() { if (location != null) { // Stock adjustment actions - if (InvenTreeAPI().checkPermission('stock', 'change')) { + if (InvenTreeAPI().checkPermission("stock", "change")) { // Scan items into location tiles.add( ListTile( @@ -430,7 +430,7 @@ List detailTiles() { class SublocationList extends StatelessWidget { final List _locations; - SublocationList(this._locations); + const SublocationList(this._locations); void _openLocation(BuildContext context, int pk) { @@ -446,7 +446,7 @@ class SublocationList extends StatelessWidget { InvenTreeStockLocation loc = _locations[index]; return ListTile( - title: Text('${loc.name}'), + title: Text("${loc.name}"), subtitle: Text("${loc.description}"), trailing: Text("${loc.itemcount}"), onTap: () { @@ -477,7 +477,7 @@ class PaginatedStockList extends StatefulWidget { final Map filters; - PaginatedStockList(this.filters); + const PaginatedStockList(this.filters); @override _PaginatedStockListState createState() => _PaginatedStockListState(filters); @@ -516,7 +516,7 @@ class _PaginatedStockListState extends State { Future _fetchPage(int pageKey) async { try { - Map params = this.filters; + Map params = filters; params["search"] = "${_searchTerm}"; diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 84641330..ad639766 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -1,8 +1,6 @@ -// Pagination related widgets - -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/l10.dart'; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; class PaginatedSearchWidget extends StatelessWidget { @@ -44,9 +42,9 @@ class PaginatedSearchWidget extends StatelessWidget { class NoResultsWidget extends StatelessWidget { - final String description; + const NoResultsWidget(this.description); - NoResultsWidget(this.description); + final String description; @override Widget build(BuildContext context) { diff --git a/lib/widget/part_attachments_widget.dart b/lib/widget/part_attachments_widget.dart index 99955e64..3af0e004 100644 --- a/lib/widget/part_attachments_widget.dart +++ b/lib/widget/part_attachments_widget.dart @@ -1,23 +1,19 @@ +import "dart:io"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/fields.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/snacks.dart"; -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:image_picker/image_picker.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/snacks.dart'; - -import 'dart:io'; - -import '../api.dart'; -import '../l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/l10.dart"; class PartAttachmentsWidget extends StatefulWidget { - PartAttachmentsWidget(this.part, {Key? key}) : super(key: key); + const PartAttachmentsWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; @@ -42,7 +38,7 @@ class _PartAttachmentDisplayState extends RefreshableState actions = []; - if (InvenTreeAPI().checkPermission('part', 'change')) { + if (InvenTreeAPI().checkPermission("part", "change")) { // File upload actions.add( diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 778a8d70..6a1cdd18 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -1,28 +1,28 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/inventree/stock.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/foundation.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/stock.dart"; -import 'package:inventree/l10.dart'; -import 'package:inventree/widget/part_attachments_widget.dart'; -import 'package:inventree/widget/part_notes.dart'; -import 'package:inventree/widget/progress.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/category_display.dart'; -import 'package:inventree/api.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/part_image_widget.dart'; -import 'package:inventree/widget/stock_detail.dart'; +import "package:inventree/l10.dart"; +import "package:inventree/widget/part_attachments_widget.dart"; +import "package:inventree/widget/part_notes.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/category_display.dart"; +import "package:inventree/api.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/part_image_widget.dart"; +import "package:inventree/widget/stock_detail.dart"; -import 'location_display.dart'; +import "pcakage:inventree/widget/location_display.dart"; class PartDetailWidget extends StatefulWidget { - PartDetailWidget(this.part, {Key? key}) : super(key: key); + const PartDetailWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; @@ -46,7 +46,7 @@ class _PartDisplayState extends RefreshableState { List actions = []; - if (InvenTreeAPI().checkPermission('part', 'view')) { + if (InvenTreeAPI().checkPermission("part", "view")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.globe), @@ -55,7 +55,7 @@ class _PartDisplayState extends RefreshableState { ); } - if (InvenTreeAPI().checkPermission('part', 'change')) { + if (InvenTreeAPI().checkPermission("part", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), @@ -89,9 +89,9 @@ class _PartDisplayState extends RefreshableState { await part.getTestTemplates(); } - void _toggleStar() async { + Future _toggleStar() async { - if (InvenTreeAPI().checkPermission('part', 'view')) { + if (InvenTreeAPI().checkPermission("part", "view")) { await part.update(values: {"starred": "${!part.starred}"}); refresh(); } @@ -327,7 +327,8 @@ class _PartDisplayState extends RefreshableState { } // TODO - Add request tests? - if (false && part.isTrackable) { + /* + if (part.isTrackable) { tiles.add(ListTile( title: Text(L10().testsRequired), leading: FaIcon(FontAwesomeIcons.tasks), @@ -336,6 +337,7 @@ class _PartDisplayState extends RefreshableState { ) ); } + */ // Notes field tiles.add( @@ -453,7 +455,7 @@ class _PartDisplayState extends RefreshableState { ); } - if (false && !part.isActive && InvenTreeAPI().checkPermission('part', 'delete')) { + if (false && !part.isActive && InvenTreeAPI().checkPermission("part", "delete")) { tiles.add( ListTile( title: Text(L10().deletePart), diff --git a/lib/widget/part_image_widget.dart b/lib/widget/part_image_widget.dart index 533072a4..63981371 100644 --- a/lib/widget/part_image_widget.dart +++ b/lib/widget/part_image_widget.dart @@ -1,23 +1,21 @@ -import 'dart:io'; +import "dart:io"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:image_picker/image_picker.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/foundation.dart"; +import "package:flutter/material.dart"; -import 'package:inventree/api.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/snacks.dart'; - -import '../l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/fields.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:inventree/l10.dart"; class PartImageWidget extends StatefulWidget { - PartImageWidget(this.part, {Key? key}) : super(key: key); + const PartImageWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; @@ -46,7 +44,7 @@ class _PartImageState extends RefreshableState { List actions = []; - if (InvenTreeAPI().checkPermission('part', 'change')) { + if (InvenTreeAPI().checkPermission("part", "change")) { // File upload actions.add( diff --git a/lib/widget/part_notes.dart b/lib/widget/part_notes.dart index 484627a0..fe4b0348 100644 --- a/lib/widget/part_notes.dart +++ b/lib/widget/part_notes.dart @@ -1,18 +1,18 @@ -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/api.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:inventree/l10.dart'; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/api.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter_markdown/flutter_markdown.dart"; +import "package:inventree/l10.dart"; class PartNotesWidget extends StatefulWidget { final InvenTreePart part; - PartNotesWidget(this.part, {Key? key}) : super(key: key); + const PartNotesWidget(this.part, {Key? key}) : super(key: key); @override _PartNotesState createState() => _PartNotesState(part); @@ -38,7 +38,7 @@ class _PartNotesState extends RefreshableState { List actions = []; - if (InvenTreeAPI().checkPermission('part', 'change')) { + if (InvenTreeAPI().checkPermission("part", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), diff --git a/lib/widget/part_suppliers.dart b/lib/widget/part_suppliers.dart index c7a06fc5..fc1b4030 100644 --- a/lib/widget/part_suppliers.dart +++ b/lib/widget/part_suppliers.dart @@ -1,19 +1,19 @@ -import 'package:inventree/l10.dart'; +import "dart:core"; -import 'package:inventree/api.dart'; +import "package:inventree/l10.dart"; -import 'dart:core'; +import "package:inventree/api.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/widget/company_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/widget/company_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; class PartSupplierWidget extends StatefulWidget { - PartSupplierWidget(this.part, {Key? key}) : super(key: key); + const PartSupplierWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; diff --git a/lib/widget/progress.dart b/lib/widget/progress.dart index 9aaeb256..92ef69c6 100644 --- a/lib/widget/progress.dart +++ b/lib/widget/progress.dart @@ -1,6 +1,6 @@ -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; /* * Construct a circular progress indicator diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 8b84e9cb..41bbaeb8 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -1,21 +1,20 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/api.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/purchase_order.dart'; -import 'package:inventree/widget/company_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import '../l10.dart'; -import 'location_display.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/purchase_order.dart"; +import "package:inventree/widget/company_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/location_display.dart"; class PurchaseOrderDetailWidget extends StatefulWidget { - PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); + const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); final InvenTreePurchaseOrder order; @@ -72,7 +71,7 @@ class _PurchaseOrderDetailState extends RefreshableState editOrder(BuildContext context) async { order.editForm( context, diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 66bf0a1d..d05010e4 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -1,23 +1,22 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/purchase_order_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; -import '../l10.dart'; - -import 'package:inventree/api.dart'; -import 'package:inventree/inventree/purchase_order.dart'; +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/purchase_order_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/api.dart"; +import "package:inventree/inventree/purchase_order.dart"; /* * Widget class for displaying a list of Purchase Orders */ class PurchaseOrderListWidget extends StatefulWidget { - PurchaseOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); + const PurchaseOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); final Map filters; @@ -100,7 +99,7 @@ class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList // Copy across provided filters for (String key in filters.keys) { - params[key] = filters[key] ?? ''; + params[key] = filters[key] ?? ""; } final page = await InvenTreePurchaseOrder().listPaginated( diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index aa288499..77508f3b 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -1,7 +1,7 @@ -import 'package:inventree/widget/drawer.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import "package:inventree/widget/drawer.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:flutter/widgets.dart"; abstract class RefreshableState extends State { @@ -32,6 +32,7 @@ abstract class RefreshableState extends State { String getAppBarTitle(BuildContext context) { return "App Bar Title"; } + @override void initState() { super.initState(); WidgetsBinding.instance?.addPostFrameCallback((_) => onBuild(_context!)); diff --git a/lib/widget/search.dart b/lib/widget/search.dart index d010c1df..a4fd9a18 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -1,17 +1,17 @@ -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/progress.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:inventree/widget/stock_detail.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/l10.dart'; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/stock_detail.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/l10.dart"; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/stock.dart'; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/stock.dart"; -import '../api.dart'; +import "package:inventree/api.dart"; // TODO - Refactor duplicate code in this file! @@ -101,7 +101,7 @@ class PartSearchDelegate extends SearchDelegate { IconButton( icon: FaIcon(FontAwesomeIcons.backspace), onPressed: () { - query = ''; + query = ""; search(context); }, ), @@ -119,7 +119,7 @@ class PartSearchDelegate extends SearchDelegate { return IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - this.close(context, null); + close(context, null); } ); } @@ -289,7 +289,7 @@ class StockSearchDelegate extends SearchDelegate { IconButton( icon: FaIcon(FontAwesomeIcons.backspace), onPressed: () { - query = ''; + query = ""; search(context); }, ), @@ -307,7 +307,7 @@ class StockSearchDelegate extends SearchDelegate { return IconButton( icon: Icon(Icons.arrow_back), onPressed: () { - this.close(context, null); + close(context, null); } ); } diff --git a/lib/widget/snacks.dart b/lib/widget/snacks.dart index 7d3e1578..6a33cee1 100644 --- a/lib/widget/snacks.dart +++ b/lib/widget/snacks.dart @@ -8,11 +8,11 @@ * | Text | */ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:one_context/one_context.dart'; -import 'package:inventree/l10.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:one_context/one_context.dart"; +import "package:inventree/l10.dart"; void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) { diff --git a/lib/widget/spinner.dart b/lib/widget/spinner.dart index 1770a90f..9b53a93e 100644 --- a/lib/widget/spinner.dart +++ b/lib/widget/spinner.dart @@ -1,8 +1,8 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/cupertino.dart'; +import "package:flutter/material.dart"; +import "package:flutter/cupertino.dart"; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/app_colors.dart'; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/app_colors.dart"; class Spinner extends StatefulWidget { final IconData? icon; diff --git a/lib/widget/starred_parts.dart b/lib/widget/starred_parts.dart index df439f79..6d15d952 100644 --- a/lib/widget/starred_parts.dart +++ b/lib/widget/starred_parts.dart @@ -1,20 +1,18 @@ +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:inventree/l10.dart"; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/progress.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -import 'package:inventree/l10.dart'; - -import '../api.dart'; +import "package:inventree/api.dart"; class StarredPartWidget extends StatefulWidget { - StarredPartWidget({Key? key}) : super(key: key); + const StarredPartWidget({Key? key}) : super(key: key); @override _StarredPartState createState() => _StarredPartState(); diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 343013df..e4431190 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -1,30 +1,30 @@ -import 'package:inventree/app_colors.dart'; -import 'package:inventree/barcode.dart'; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/widget/location_display.dart'; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/progress.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:inventree/widget/snacks.dart'; -import 'package:inventree/widget/stock_item_test_results.dart'; -import 'package:inventree/widget/stock_notes.dart'; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; +import "package:inventree/app_colors.dart"; +import "package:inventree/barcode.dart"; +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/dialogs.dart"; +import "package:inventree/widget/fields.dart"; +import "package:inventree/widget/location_display.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/snacks.dart"; +import "package:inventree/widget/stock_item_test_results.dart"; +import "package:inventree/widget/stock_notes.dart"; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; -import 'package:inventree/api.dart'; +import "package:inventree/api.dart"; -import 'package:dropdown_search/dropdown_search.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import "package:dropdown_search/dropdown_search.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; class StockDetailWidget extends StatefulWidget { - StockDetailWidget(this.item, {Key? key}) : super(key: key); + const StockDetailWidget(this.item, {Key? key}) : super(key: key); final InvenTreeStockItem item; @@ -53,7 +53,7 @@ class _StockItemDisplayState extends RefreshableState { List actions = []; - if (InvenTreeAPI().checkPermission('stock', 'view')) { + if (InvenTreeAPI().checkPermission("stock", "view")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.globe), @@ -62,7 +62,7 @@ class _StockItemDisplayState extends RefreshableState { ); } - if (InvenTreeAPI().checkPermission('stock', 'change')) { + if (InvenTreeAPI().checkPermission("stock", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), @@ -99,13 +99,13 @@ class _StockItemDisplayState extends RefreshableState { await item.reload(); // Request part information - part = await InvenTreePart().get(item.partId) as InvenTreePart; + part = await InvenTreePart().get(item.partId) as InvenTreePart?; // Request test results... await item.getTestResults(); } - void _editStockItem(BuildContext context) async { + Future _editStockItem(BuildContext context) async { var fields = InvenTreeStockItem().formFields(); @@ -125,7 +125,7 @@ class _StockItemDisplayState extends RefreshableState { } - void _addStock() async { + Future _addStock() async { double quantity = double.parse(_quantityController.text); _quantityController.clear(); @@ -138,7 +138,7 @@ class _StockItemDisplayState extends RefreshableState { refresh(); } - void _addStockDialog() async { + Future _addStockDialog() async { _quantityController.clear(); _notesController.clear(); @@ -171,7 +171,7 @@ class _StockItemDisplayState extends RefreshableState { } } - void _removeStock() async { + Future _removeStock() async { double quantity = double.parse(_quantityController.text); _quantityController.clear(); @@ -211,7 +211,7 @@ class _StockItemDisplayState extends RefreshableState { ); } - void _countStock() async { + Future _countStock() async { double quantity = double.parse(_quantityController.text); _quantityController.clear(); @@ -223,7 +223,7 @@ class _StockItemDisplayState extends RefreshableState { refresh(); } - void _countStockDialog() async { + Future _countStockDialog() async { _quantityController.text = item.quantity.toString(); _notesController.clear(); @@ -251,9 +251,9 @@ class _StockItemDisplayState extends RefreshableState { } - void _unassignBarcode(BuildContext context) async { + Future _unassignBarcode(BuildContext context) async { - final bool result = await item.update(values: {'uid': ''}); + final bool result = await item.update(values: {"uid": ""}); if (result) { showSnackIcon( @@ -271,7 +271,7 @@ class _StockItemDisplayState extends RefreshableState { } - void _transferStock(int locationId) async { + Future _transferStock(int locationId) async { double quantity = double.tryParse(_quantityController.text) ?? item.quantity; String notes = _notesController.text; @@ -288,7 +288,7 @@ class _StockItemDisplayState extends RefreshableState { } } - void _transferStockDialog(BuildContext context) async { + Future _transferStockDialog(BuildContext context) async { int? location_pk; @@ -349,13 +349,13 @@ class _StockItemDisplayState extends RefreshableState { hint: L10().searchLocation, onChanged: null, itemAsString: (dynamic location) { - return (location['pathstring'] ?? '') as String; + return (location["pathstring"] ?? "") as String; }, onSaved: (dynamic location) { if (location == null) { location_pk = null; } else { - location_pk = location['pk'] as int; + location_pk = location["pk"] as int; } }, isFilteredOnline: true, @@ -503,7 +503,8 @@ class _StockItemDisplayState extends RefreshableState { // Supplier part? // TODO: Display supplier part info page? - if (false && item.supplierPartId > 0) { + /* + if (item.supplierPartId > 0) { tiles.add( ListTile( title: Text("${item.supplierName}"), @@ -514,6 +515,7 @@ class _StockItemDisplayState extends RefreshableState { ) ); } + */ if (item.link.isNotEmpty) { tiles.add( @@ -559,7 +561,8 @@ class _StockItemDisplayState extends RefreshableState { // TODO - Is this stock item linked to a PurchaseOrder? // TODO - Re-enable stock item history display - if (false && item.trackingItemCount > 0) { + /* + if (item.trackingItemCount > 0) { tiles.add( ListTile( title: Text(L10().history), @@ -574,6 +577,7 @@ class _StockItemDisplayState extends RefreshableState { ) ); } + */ // Notes field tiles.add( @@ -600,7 +604,7 @@ class _StockItemDisplayState extends RefreshableState { tiles.add(headerTile()); // First check that the user has the required permissions to adjust stock - if (!InvenTreeAPI().checkPermission('stock', 'change')) { + if (!InvenTreeAPI().checkPermission("stock", "change")) { tiles.add( ListTile( title: Text(L10().permissionRequired), @@ -710,12 +714,11 @@ class _StockItemDisplayState extends RefreshableState { items: [ BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.infoCircle), - title: Text(L10().details), + label: L10().details, ), BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.wrench), - title: Text(L10().actions), - ), + label: L10().actions, ), ] ); } diff --git a/lib/widget/stock_item_test_results.dart b/lib/widget/stock_item_test_results.dart index 5c8ce974..3651fd2c 100644 --- a/lib/widget/stock_item_test_results.dart +++ b/lib/widget/stock_item_test_results.dart @@ -1,27 +1,21 @@ -import 'package:inventree/api_form.dart'; -import 'package:inventree/app_colors.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/api.dart'; -import 'package:inventree/widget/dialogs.dart'; -import 'package:inventree/widget/fields.dart'; -import 'package:inventree/widget/progress.dart'; -import 'package:inventree/widget/snacks.dart'; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/inventree/model.dart"; +import "package:inventree/api.dart"; +import "package:inventree/widget/progress.dart"; -import 'package:inventree/l10.dart'; +import "package:inventree/l10.dart"; -import 'dart:io'; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; class StockItemTestResultsWidget extends StatefulWidget { - StockItemTestResultsWidget(this.item, {Key? key}) : super(key: key); + const StockItemTestResultsWidget(this.item, {Key? key}) : super(key: key); final InvenTreeStockItem item; @@ -32,8 +26,6 @@ class StockItemTestResultsWidget extends StatefulWidget { class _StockItemTestResultDisplayState extends RefreshableState { - final _addResultKey = GlobalKey(); - @override String getAppBarTitle(BuildContext context) => L10().testResults; @@ -59,7 +51,7 @@ class _StockItemTestResultDisplayState extends RefreshableState addTestResult(BuildContext context, {String name = "", bool nameIsEditable = true, bool result = false, String value = "", bool valueRequired = false, bool attachmentRequired = false}) async { InvenTreeStockItemTestResult().createForm( context, @@ -165,7 +157,6 @@ class _StockItemTestResultDisplayState extends RefreshableState _StockNotesState(item); @@ -39,7 +39,7 @@ class _StockNotesState extends RefreshableState { List getAppBarActions(BuildContext context) { List actions = []; - if (InvenTreeAPI().checkPermission('stock', 'change')) { + if (InvenTreeAPI().checkPermission("stock", "change")) { actions.add( IconButton( icon: FaIcon(FontAwesomeIcons.edit), diff --git a/lib/widget/submit_feedback.dart b/lib/widget/submit_feedback.dart index b335d6f0..201929c3 100644 --- a/lib/widget/submit_feedback.dart +++ b/lib/widget/submit_feedback.dart @@ -1,12 +1,10 @@ +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/inventree/sentry.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'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/snacks.dart'; - -import '../l10.dart'; +import "package:inventree/l10.dart"; class SubmitFeedbackWidget extends StatefulWidget { @@ -18,7 +16,7 @@ class SubmitFeedbackWidget extends StatefulWidget { class _SubmitFeedbackState extends State { - final _formkey = new GlobalKey(); + final _formkey = GlobalKey(); String message = ""; @@ -61,8 +59,6 @@ class _SubmitFeedbackState extends State { key: _formkey, child: SingleChildScrollView( child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( diff --git a/pubspec.yaml b/pubspec.yaml index 230d6628..5b9a46bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,41 +13,42 @@ environment: sdk: ">=2.12.0 <3.0.0" dependencies: + + audioplayers: ^0.20.1 # Play audio files + cached_network_image: ^3.1.0 # Download and cache remote images + camera: # Camera + cupertino_icons: ^1.0.3 + date_field: ^2.1.2 # Date / time picker + device_info_plus: ^2.1.0 # Information about the device + dropdown_search: 0.6.3 # Dropdown autocomplete form fields + file_picker: ^4.0.0 # Select files from the device + flutter: sdk: flutter flutter_localizations: sdk: flutter - intl: ^0.17.0 - - cupertino_icons: ^1.0.3 - http: ^0.13.0 - cached_network_image: ^3.1.0 # Download and cache remote images - qr_code_scanner: ^0.5.2 # Barcode scanning - package_info_plus: ^1.0.4 # App information introspection - device_info_plus: ^2.1.0 # Information about the device - font_awesome_flutter: ^9.1.0 # FontAwesome icon set - sentry_flutter: 5.0.0 # Error reporting - image_picker: ^0.8.3 # Select or take photos - file_picker: ^4.0.0 # Select files from the device - date_field: ^2.1.2 # Date / time picker - url_launcher: 6.0.9 # Open link in system browser - open_file: 3.2.1 # Open local files flutter_markdown: ^0.6.2 # Rendering markdown - camera: # Camera - path_provider: 2.0.2 # Local file storage - sembast: ^3.1.0+2 # NoSQL data storage - one_context: ^1.1.0 # Dialogs without requiring context + font_awesome_flutter: ^9.1.0 # FontAwesome icon set + http: ^0.13.0 + image_picker: ^0.8.3 # Select or take photos infinite_scroll_pagination: ^3.1.0 # Let the server do all the work! - audioplayers: ^0.20.1 # Play audio files - dropdown_search: 0.6.3 # Dropdown autocomplete form fields + intl: ^0.17.0 + one_context: ^1.1.0 # Dialogs without requiring context + open_file: 3.2.1 # Open local files + package_info_plus: ^1.0.4 # App information introspection path: + path_provider: 2.0.2 # Local file storage + qr_code_scanner: ^0.5.2 # Barcode scanning + sembast: ^3.1.0+2 # NoSQL data storage + sentry_flutter: 5.0.0 # Error reporting + url_launcher: 6.0.9 # Open link in system browser dev_dependencies: + flutter_launcher_icons: flutter_test: sdk: flutter - flutter_launcher_icons: lint: ^1.0.0 flutter_icons: diff --git a/test/widget_test.dart b/test/widget_test.dart index 661b665f..3c73102a 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -5,9 +5,9 @@ // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. -import 'package:flutter_test/flutter_test.dart'; +import "package:flutter_test/flutter_test.dart"; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets("Counter increments smoke test", (WidgetTester tester) async { }); } From 77bac9af362bb33a5ad2414d50d5e5c79e68a2e3 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 20:35:19 +1000 Subject: [PATCH 24/70] Yet more linting --- analysis_options.yaml | 2 + lib/api.dart | 27 ++++++------- lib/api_form.dart | 49 ++++++++++++---------- lib/app_settings.dart | 18 ++++----- lib/barcode.dart | 42 +++++++++---------- lib/inventree/company.dart | 21 +++++----- lib/inventree/model.dart | 33 +++++++-------- lib/inventree/part.dart | 39 +++++++++--------- lib/inventree/purchase_order.dart | 18 ++++----- lib/inventree/stock.dart | 54 +++++++++++-------------- lib/preferences.dart | 17 ++++---- lib/settings/about.dart | 4 +- lib/settings/app_settings.dart | 4 +- lib/settings/login.dart | 20 ++++----- lib/settings/release.dart | 8 ++-- lib/user_profile.dart | 20 ++++----- lib/widget/category_display.dart | 14 +++---- lib/widget/company_detail.dart | 4 +- lib/widget/drawer.dart | 4 +- lib/widget/home.dart | 6 +-- lib/widget/location_display.dart | 15 +++---- lib/widget/paginator.dart | 4 +- lib/widget/part_detail.dart | 4 +- lib/widget/part_notes.dart | 8 ++-- lib/widget/search.dart | 46 ++++++++++----------- lib/widget/spinner.dart | 7 ++-- lib/widget/stock_detail.dart | 4 +- lib/widget/stock_item_test_results.dart | 4 +- lib/widget/stock_notes.dart | 8 ++-- 29 files changed, 251 insertions(+), 253 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 92962f4e..07b40c28 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -55,5 +55,7 @@ linter: sort_child_properties_last: false + directives_ordering: false + # Blindly follow the Flutter code style, which prefers types everywhere always_specify_types: false diff --git a/lib/api.dart b/lib/api.dart index b89a95fa..96202c3d 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -50,7 +50,7 @@ class APIResponse { bool clientError() => (statusCode >= 400) && (statusCode < 500); - bool serverError() => (statusCode >= 500); + bool serverError() => statusCode >= 500; bool isMap() { return data != null && data is Map; @@ -86,8 +86,6 @@ class APIResponse { */ class InvenTreeFileService extends FileService { - HttpClient? _client; - InvenTreeFileService({HttpClient? client, bool strictHttps = false}) { _client = client ?? HttpClient(); @@ -99,6 +97,8 @@ class InvenTreeFileService extends FileService { } } + HttpClient? _client; + @override Future get(String url, {Map? headers}) async { @@ -133,6 +133,12 @@ class InvenTreeFileService extends FileService { class InvenTreeAPI { + factory InvenTreeAPI() { + return _api; + } + + InvenTreeAPI._internal(); + // Minimum required API version for server static const _minApiVersion = 7; @@ -236,14 +242,7 @@ class InvenTreeAPI { } // Ensure we only ever create a single instance of the API class - static final InvenTreeAPI _api = new InvenTreeAPI._internal(); - - factory InvenTreeAPI() { - return _api; - } - - InvenTreeAPI._internal(); - + static final InvenTreeAPI _api = InvenTreeAPI._internal(); /* * Connect to the remote InvenTree server: @@ -481,7 +480,7 @@ class InvenTreeAPI { // Perform a PATCH request Future patch(String url, {Map body = const {}, int? expectedStatusCode}) async { - var _body = Map(); + Map _body = {}; // Copy across provided data body.forEach((K, V) => _body[K] = V); @@ -939,7 +938,7 @@ class InvenTreeAPI { // Return a list of request headers Map defaultHeaders() { - var headers = Map(); + Map headers = {}; headers[HttpHeaders.authorizationHeader] = _authorizationHeader(); headers[HttpHeaders.acceptHeader] = "application/json"; @@ -983,7 +982,7 @@ class InvenTreeAPI { ) ); - return new CachedNetworkImage( + return CachedNetworkImage( imageUrl: url, placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => FaIcon(FontAwesomeIcons.timesCircle, color: COLOR_DANGER), diff --git a/lib/api_form.dart b/lib/api_form.dart index 93fd5171..70971a13 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -209,7 +209,7 @@ class APIFormField { // Field for selecting and uploading files Widget _constructFileField() { - TextEditingController controller = new TextEditingController(); + TextEditingController controller = TextEditingController(); controller.text = (attachedfile?.path ?? L10().attachmentSelect).split("/").last; @@ -506,7 +506,7 @@ class APIFormField { } TextStyle _labelStyle() { - return new TextStyle( + return TextStyle( fontWeight: FontWeight.bold, fontSize: 18, fontFamily: "arial", @@ -516,7 +516,7 @@ class APIFormField { } TextStyle _helperStyle() { - return new TextStyle( + return TextStyle( fontStyle: FontStyle.italic, color: hasErrors() ? COLOR_DANGER : COLOR_GRAY, ); @@ -660,6 +660,18 @@ Future launchApiForm(BuildContext context, String title, String url, Map)? onSuccess; - APIFormWidget( - this.title, - this.url, - this.fields, - this.method, - { - Key? key, - this.onSuccess, - this.fileField = "", - } - ) : super(key: key); - @override _APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, method, onSuccess, fileField); @@ -695,7 +695,9 @@ class APIFormWidget extends StatefulWidget { class _APIFormWidgetState extends State { - final _formKey = new GlobalKey(); + _APIFormWidgetState(this.title, this.url, this.fields, this.method, this.onSuccess, this.fileField) : super(); + + final _formKey = GlobalKey(); String title; @@ -709,8 +711,6 @@ class _APIFormWidgetState extends State { Function(Map)? onSuccess; - _APIFormWidgetState(this.title, this.url, this.fields, this.method, this.onSuccess, this.fileField) : super(); - bool spacerRequired = false; List _buildForm() { @@ -788,29 +788,36 @@ class _APIFormWidgetState extends State { if (file != null) { // A valid file has been supplied - return await InvenTreeAPI().uploadFile( + final response = await InvenTreeAPI().uploadFile( url, file, name: fileField, fields: data, ); + + return response; } } } } if (method == "POST") { - return await InvenTreeAPI().post( + final response = await InvenTreeAPI().post( url, body: data, expectedStatusCode: null ); + + return response; + } else { - return await InvenTreeAPI().patch( + final response = await InvenTreeAPI().patch( url, body: data, expectedStatusCode: null ); + + return response; } } diff --git a/lib/app_settings.dart b/lib/app_settings.dart index c2c672d8..f47b41d3 100644 --- a/lib/app_settings.dart +++ b/lib/app_settings.dart @@ -7,9 +7,15 @@ import "package:inventree/preferences.dart"; class InvenTreeSettingsManager { + factory InvenTreeSettingsManager() { + return _manager; + } + + InvenTreeSettingsManager._internal(); + final store = StoreRef("settings"); - Future get _db async => await InvenTreePreferencesDB.instance.database; + Future get _db async => InvenTreePreferencesDB.instance.database; Future getValue(String key, dynamic backup) async { @@ -39,11 +45,5 @@ class InvenTreeSettingsManager { } // Ensure we only ever create a single instance of this class - static final InvenTreeSettingsManager _manager = new InvenTreeSettingsManager._internal(); - - factory InvenTreeSettingsManager() { - return _manager; - } - - InvenTreeSettingsManager._internal(); -} \ No newline at end of file + static final InvenTreeSettingsManager _manager = InvenTreeSettingsManager._internal(); +} diff --git a/lib/barcode.dart b/lib/barcode.dart index e3069dc3..3f44aa9c 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -32,21 +32,21 @@ class BarcodeHandler { * based on the response returned from the InvenTree server */ - String getOverlayText(BuildContext context) => "Barcode Overlay"; + BarcodeHandler(); - BarcodeHandler(); + String getOverlayText(BuildContext context) => "Barcode Overlay"; - QRViewController? _controller; + QRViewController? _controller; - Future successTone() async { + Future successTone() async { - final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; + final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; - if (en) { - final player = AudioCache(); - player.play("sounds/barcode_scan.mp3"); - } + if (en) { + final player = AudioCache(); + player.play("sounds/barcode_scan.mp3"); } + } Future failureTone() async { @@ -266,10 +266,10 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { * Barcode handler for assigning a new barcode to a stock item */ - final InvenTreeStockItem item; - StockItemBarcodeAssignmentHandler(this.item); + final InvenTreeStockItem item; + @override String getOverlayText(BuildContext context) => L10().barcodeScanAssign; @@ -334,10 +334,10 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler { * Barcode handler for scanning a provided StockItem into a scanned StockLocation */ - final InvenTreeStockItem item; - StockItemScanIntoLocationHandler(this.item); + final InvenTreeStockItem item; + @override String getOverlayText(BuildContext context) => L10().barcodeScanLocation; @@ -396,11 +396,11 @@ class StockLocationScanInItemsHandler extends BarcodeHandler { /* * Barcode handler for scanning stock item(s) into the specified StockLocation */ - - final InvenTreeStockLocation location; - + StockLocationScanInItemsHandler(this.location); - + + final InvenTreeStockLocation location; + @override String getOverlayText(BuildContext context) => L10().barcodeScanItem; @@ -466,10 +466,10 @@ class StockLocationScanInItemsHandler extends BarcodeHandler { class InvenTreeQRView extends StatefulWidget { - final BarcodeHandler _handler; - const InvenTreeQRView(this._handler, {Key? key}) : super(key: key); + final BarcodeHandler _handler; + @override State createState() => _QRViewState(_handler); } @@ -477,6 +477,8 @@ class InvenTreeQRView extends StatefulWidget { class _QRViewState extends State { + _QRViewState(this._handler) : super(); + final GlobalKey qrKey = GlobalKey(debugLabel: "QR"); QRViewController? _controller; @@ -496,8 +498,6 @@ class _QRViewState extends State { _controller!.resumeCamera(); } - _QRViewState(this._handler) : super(); - void _onViewCreated(BuildContext context, QRViewController controller) { _controller = controller; controller.scannedDataStream.listen((barcode) { diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 332db292..a749d233 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -10,6 +10,8 @@ class InvenTreeCompany extends InvenTreeModel { InvenTreeCompany() : super(); + InvenTreeCompany.fromJson(Map json) : super.fromJson(json); + @override String get URL => "company/"; @@ -42,8 +44,6 @@ class InvenTreeCompany extends InvenTreeModel { bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; - InvenTreeCompany.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { var company = InvenTreeCompany.fromJson(json); @@ -57,6 +57,11 @@ class InvenTreeCompany extends InvenTreeModel { * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database */ class InvenTreeSupplierPart extends InvenTreeModel { + + InvenTreeSupplierPart() : super(); + + InvenTreeSupplierPart.fromJson(Map json) : super.fromJson(json); + @override String get URL => "company/part/"; @@ -78,10 +83,6 @@ class InvenTreeSupplierPart extends InvenTreeModel { return _filters(); } - InvenTreeSupplierPart() : super(); - - InvenTreeSupplierPart.fromJson(Map json) : super.fromJson(json); - int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; String get manufacturerName => (jsondata["manufacturer_detail"]["name"] ?? "") as String; @@ -117,6 +118,10 @@ class InvenTreeSupplierPart extends InvenTreeModel { class InvenTreeManufacturerPart extends InvenTreeModel { + InvenTreeManufacturerPart() : super(); + + InvenTreeManufacturerPart.fromJson(Map json) : super.fromJson(json); + @override String url = "company/part/manufacturer/"; @@ -127,10 +132,6 @@ class InvenTreeManufacturerPart extends InvenTreeModel { }; } - InvenTreeManufacturerPart() : super(); - - InvenTreeManufacturerPart.fromJson(Map json) : super.fromJson(json); - int get partId => (jsondata["part"] ?? -1) as int; int get manufacturerId => (jsondata["manufacturer"] ?? -1) as int; diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 1a5a1a03..bd1517ec 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -39,6 +39,11 @@ class InvenTreePageResponse { */ class InvenTreeModel { + InvenTreeModel(); + + // Construct an InvenTreeModel from a JSON data object + InvenTreeModel.fromJson(this.jsondata); + // Override the endpoint URL for each subclass String get URL => ""; @@ -115,19 +120,6 @@ class InvenTreeModel { // Accessor for the API var api = InvenTreeAPI(); - // Default empty object constructor - InvenTreeModel() { - jsondata.clear(); - } - - // Construct an InvenTreeModel from a JSON data object - InvenTreeModel.fromJson(Map json) { - - // Store the json object - jsondata = json; - - } - int get pk => (jsondata["pk"] ?? -1) as int; // Some common accessors @@ -185,10 +177,14 @@ class InvenTreeModel { } - Map defaultListFilters() { return Map(); } + Map defaultListFilters() { + return {}; + } // A map of "default" headers to use when performing a GET request - Map defaultGetFilters() { return Map(); } + Map defaultGetFilters() { + return {}; + } /* * Reload this object, by requesting data from the server @@ -357,7 +353,7 @@ class InvenTreeModel { } // Construct the response - InvenTreePageResponse page = new InvenTreePageResponse(); + InvenTreePageResponse page = InvenTreePageResponse(); var data = response.asMap(); @@ -458,9 +454,10 @@ class InvenTreeModel { class InvenTreeAttachment extends InvenTreeModel { // Class representing an "attachment" file - InvenTreeAttachment() : super(); + InvenTreeAttachment.fromJson(Map json) : super.fromJson(json); + String get attachment => (jsondata["attachment"] ?? "") as String; // Return the filename of the attachment @@ -509,8 +506,6 @@ class InvenTreeAttachment extends InvenTreeModel { } } - InvenTreeAttachment.fromJson(Map json) : super.fromJson(json); - Future uploadAttachment(File attachment, {String comment = "", Map fields = const {}}) async { final APIResponse response = await InvenTreeAPI().uploadFile( diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 118a7c33..804615e6 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -10,6 +10,10 @@ import "model.dart"; class InvenTreePartCategory extends InvenTreeModel { + InvenTreePartCategory() : super(); + + InvenTreePartCategory.fromJson(Map json) : super.fromJson(json); + @override String get URL => "part/category/"; @@ -25,12 +29,11 @@ class InvenTreePartCategory extends InvenTreeModel { @override Map defaultListFilters() { - var filters = new Map(); - filters["active"] = "true"; - filters["cascade"] = "false"; - - return filters; + return { + "active": "true", + "cascade": "false" + }; } String get pathstring => (jsondata["pathstring"] ?? "") as String; @@ -54,10 +57,6 @@ class InvenTreePartCategory extends InvenTreeModel { int get partcount => (jsondata["parts"] ?? 0) as int; - InvenTreePartCategory() : super(); - - InvenTreePartCategory.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { var cat = InvenTreePartCategory.fromJson(json); @@ -71,6 +70,10 @@ class InvenTreePartCategory extends InvenTreeModel { class InvenTreePartTestTemplate extends InvenTreeModel { + InvenTreePartTestTemplate() : super(); + + InvenTreePartTestTemplate.fromJson(Map json) : super.fromJson(json); + @override String get URL => "part/test-template/"; @@ -84,10 +87,6 @@ class InvenTreePartTestTemplate extends InvenTreeModel { bool get requiresAttachment => (jsondata["requires_attachment"] ?? false) as bool; - InvenTreePartTestTemplate() : super(); - - InvenTreePartTestTemplate.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { var template = InvenTreePartTestTemplate.fromJson(json); @@ -123,6 +122,10 @@ class InvenTreePartTestTemplate extends InvenTreeModel { class InvenTreePart extends InvenTreeModel { + InvenTreePart() : super(); + + InvenTreePart.fromJson(Map json) : super.fromJson(json); + @override String get URL => "part/"; @@ -377,12 +380,6 @@ class InvenTreePart extends InvenTreeModel { // Return the "starred" status of this part bool get starred => (jsondata["starred"] ?? false) as bool; - InvenTreePart() : super(); - - InvenTreePart.fromJson(Map json) : super.fromJson(json) { - // TODO - } - @override InvenTreeModel createFromJson(Map json) { @@ -397,11 +394,11 @@ class InvenTreePartAttachment extends InvenTreeAttachment { InvenTreePartAttachment() : super(); + InvenTreePartAttachment.fromJson(Map json) : super.fromJson(json); + @override String get URL => "part/attachment/"; - InvenTreePartAttachment.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { return InvenTreePartAttachment.fromJson(json); diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index b7698d36..349066e3 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -13,6 +13,10 @@ const int PO_STATUS_RETURNED = 60; class InvenTreePurchaseOrder extends InvenTreeModel { + InvenTreePurchaseOrder() : super(); + + InvenTreePurchaseOrder.fromJson(Map json) : super.fromJson(json); + @override String get URL => "order/po/"; @@ -28,8 +32,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel { }; } - InvenTreePurchaseOrder() : super(); - @override Map defaultGetFilters() { return { @@ -102,8 +104,6 @@ class InvenTreePurchaseOrder extends InvenTreeModel { return items; } - InvenTreePurchaseOrder.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { return InvenTreePurchaseOrder.fromJson(json); @@ -111,6 +111,11 @@ class InvenTreePurchaseOrder extends InvenTreeModel { } class InvenTreePOLineItem extends InvenTreeModel { + + InvenTreePOLineItem() : super(); + + InvenTreePOLineItem.fromJson(Map json) : super.fromJson(json); + @override String get URL => "order/po-line/"; @@ -189,11 +194,6 @@ class InvenTreePOLineItem extends InvenTreeModel { Map get destinationDetail => (jsondata["destination_detail"] ?? {}) as Map; - InvenTreePOLineItem() : super(); - - InvenTreePOLineItem.fromJson(Map json) - : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { return InvenTreePOLineItem.fromJson(json); diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 87f97df2..cbfe7142 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -12,6 +12,10 @@ import "package:inventree/api.dart"; class InvenTreeStockItemTestResult extends InvenTreeModel { + InvenTreeStockItemTestResult() : super(); + + InvenTreeStockItemTestResult.fromJson(Map json) : super.fromJson(json); + @override String get URL => "stock/test/"; @@ -41,10 +45,6 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { String get date => (jsondata["date"] ?? "") as String; - InvenTreeStockItemTestResult() : super(); - - InvenTreeStockItemTestResult.fromJson(Map json) : super.fromJson(json); - @override InvenTreeStockItemTestResult createFromJson(Map json) { var result = InvenTreeStockItemTestResult.fromJson(json); @@ -56,6 +56,10 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { class InvenTreeStockItem extends InvenTreeModel { + InvenTreeStockItem() : super(); + + InvenTreeStockItem.fromJson(Map json) : super.fromJson(json); + // Stock status codes static const int OK = 10; static const int ATTENTION = 50; @@ -128,33 +132,23 @@ class InvenTreeStockItem extends InvenTreeModel { @override Map defaultGetFilters() { - var headers = new Map(); - - headers["part_detail"] = "true"; - headers["location_detail"] = "true"; - headers["supplier_detail"] = "true"; - headers["cascade"] = "false"; - - return headers; + return { + "part_detail": "true", + "location_detail": "true", + "supplier_detail": "true", + "cascade": "false" + }; } @override Map defaultListFilters() { - var headers = new Map(); - - headers["part_detail"] = "true"; - headers["location_detail"] = "true"; - headers["supplier_detail"] = "true"; - headers["cascade"] = "false"; - - return headers; - } - - InvenTreeStockItem() : super(); - - InvenTreeStockItem.fromJson(Map json) : super.fromJson(json) { - // TODO + return { + "part_detail": "true", + "location_detail": "true", + "supplier_detail": "true", + "cascade": "false" + }; } List testTemplates = []; @@ -533,6 +527,10 @@ class InvenTreeStockItem extends InvenTreeModel { class InvenTreeStockLocation extends InvenTreeModel { + InvenTreeStockLocation() : super(); + + InvenTreeStockLocation.fromJson(Map json) : super.fromJson(json); + @override String get URL => "stock/location/"; @@ -566,10 +564,6 @@ class InvenTreeStockLocation extends InvenTreeModel { int get itemcount => (jsondata["items"] ?? 0) as int; - InvenTreeStockLocation() : super(); - - InvenTreeStockLocation.fromJson(Map json) : super.fromJson(json); - @override InvenTreeModel createFromJson(Map json) { diff --git a/lib/preferences.dart b/lib/preferences.dart index f35f7ab6..2c52a88d 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -11,12 +11,12 @@ import "package:path/path.dart"; */ class InvenTreePreferencesDB { + InvenTreePreferencesDB._(); + static final InvenTreePreferencesDB _singleton = InvenTreePreferencesDB._(); static InvenTreePreferencesDB get instance => _singleton; - InvenTreePreferencesDB._(); - Completer _dbOpenCompleter = Completer(); bool isOpen = false; @@ -56,6 +56,12 @@ class InvenTreePreferencesDB { class InvenTreePreferences { + factory InvenTreePreferences() { + return _api; + } + + InvenTreePreferences._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. @@ -74,11 +80,6 @@ class InvenTreePreferences { bool expandStockList = true; // Ensure we only ever create a single instance of the preferences class - static final InvenTreePreferences _api = new InvenTreePreferences._internal(); + static final InvenTreePreferences _api = InvenTreePreferences._internal(); - factory InvenTreePreferences() { - return _api; - } - - InvenTreePreferences._internal(); } \ No newline at end of file diff --git a/lib/settings/about.dart b/lib/settings/about.dart index 87ba5c2f..cde55bf5 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -12,10 +12,10 @@ import "package:inventree/l10.dart"; class InvenTreeAboutWidget extends StatelessWidget { - final PackageInfo info; - const InvenTreeAboutWidget(this.info) : super(); + final PackageInfo info; + Future _releaseNotes(BuildContext context) async { // Load release notes from external file diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index 4cee024a..07d6dfd3 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -15,10 +15,10 @@ class InvenTreeAppSettingsWidget extends StatefulWidget { class _InvenTreeAppSettingsState extends State { - final GlobalKey<_InvenTreeAppSettingsState> _settingsKey = GlobalKey<_InvenTreeAppSettingsState>(); - _InvenTreeAppSettingsState(); + final GlobalKey<_InvenTreeAppSettingsState> _settingsKey = GlobalKey<_InvenTreeAppSettingsState>(); + bool barcodeSounds = true; bool serverSounds = true; bool partSubcategory = false; diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 59487df4..32cdc35c 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -17,14 +17,14 @@ class InvenTreeLoginSettingsWidget extends StatefulWidget { class _InvenTreeLoginSettingsState extends State { - final GlobalKey<_InvenTreeLoginSettingsState> _loginKey = GlobalKey<_InvenTreeLoginSettingsState>(); - - List profiles = []; - _InvenTreeLoginSettingsState() { _reload(); } + final GlobalKey<_InvenTreeLoginSettingsState> _loginKey = GlobalKey<_InvenTreeLoginSettingsState>(); + + List profiles = []; + Future _reload() async { profiles = await UserProfileDBManager().getAllProfiles(); @@ -237,21 +237,21 @@ class _InvenTreeLoginSettingsState extends State { class ProfileEditWidget extends StatefulWidget { - UserProfile? profile; - ProfileEditWidget(this.profile) : super(); + UserProfile? profile; + @override _ProfileEditState createState() => _ProfileEditState(profile); } class _ProfileEditState extends State { - UserProfile? profile; - _ProfileEditState(this.profile) : super(); - final formKey = new GlobalKey(); + UserProfile? profile; + + final formKey = GlobalKey(); String name = ""; String server = ""; @@ -359,7 +359,7 @@ class _ProfileEditState extends State { if (uri.hasScheme) { print("Scheme: ${uri.scheme}"); - if (!(["http", "https"].contains(uri.scheme.toLowerCase()))) { + if (!["http", "https"].contains(uri.scheme.toLowerCase())) { return L10().serverStart; } } else { diff --git a/lib/settings/release.dart b/lib/settings/release.dart index b2589a85..35e60a44 100644 --- a/lib/settings/release.dart +++ b/lib/settings/release.dart @@ -6,10 +6,10 @@ import "package:inventree/l10.dart"; class ReleaseNotesWidget extends StatelessWidget { - final String releaseNotes; - const ReleaseNotesWidget(this.releaseNotes); + final String releaseNotes; + @override Widget build (BuildContext context) { return Scaffold( @@ -27,10 +27,10 @@ class ReleaseNotesWidget extends StatelessWidget { class CreditsWidget extends StatelessWidget { - final String credits; - const CreditsWidget(this.credits); + final String credits; + @override Widget build (BuildContext context) { return Scaffold( diff --git a/lib/user_profile.dart b/lib/user_profile.dart index 26e055b6..3aac40bf 100644 --- a/lib/user_profile.dart +++ b/lib/user_profile.dart @@ -16,6 +16,15 @@ class UserProfile { this.selected = false, }); + factory UserProfile.fromJson(int key, Map json, bool isSelected) => UserProfile( + key: key, + name: json["name"] as String, + server: json["server"] as String, + username: json["username"] as String, + password: json["password"] as String, + selected: isSelected, + ); + // ID of the profile int? key; @@ -36,15 +45,6 @@ class UserProfile { // User ID (will be provided by the server on log-in) int user_id = -1; - factory UserProfile.fromJson(int key, Map json, bool isSelected) => UserProfile( - key: key, - name: json["name"] as String, - server: json["server"] as String, - username: json["username"] as String, - password: json["password"] as String, - selected: isSelected, - ); - Map toJson() => { "name": name, "server": server, @@ -62,7 +62,7 @@ class UserProfileDBManager { final store = StoreRef("profiles"); - Future get _db async => await InvenTreePreferencesDB.instance.database; + Future get _db async => InvenTreePreferencesDB.instance.database; Future profileNameExists(String name) async { diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index f3d60034..80ce8605 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -32,6 +32,7 @@ class CategoryDisplayWidget extends StatefulWidget { class _CategoryDisplayState extends RefreshableState { + _CategoryDisplayState(this.category); @override String getAppBarTitle(BuildContext context) => L10().partCategory; @@ -74,8 +75,6 @@ class _CategoryDisplayState extends RefreshableState { ); } - _CategoryDisplayState(this.category); - // The local InvenTreePartCategory object final InvenTreePartCategory? category; @@ -350,10 +349,11 @@ class _CategoryDisplayState extends RefreshableState { * Builder for displaying a list of PartCategory objects */ class SubcategoryList extends StatelessWidget { - final List _categories; const SubcategoryList(this._categories); + final List _categories; + void _openCategory(BuildContext context, int pk) { // Attempt to load the sub-category. @@ -397,12 +397,12 @@ class SubcategoryList extends StatelessWidget { class PaginatedPartList extends StatefulWidget { + PaginatedPartList(this.filters, {this.onTotalChanged}); + final Map filters; Function(int)? onTotalChanged; - PaginatedPartList(this.filters, {this.onTotalChanged}); - @override _PaginatedPartListState createState() => _PaginatedPartListState(filters, onTotalChanged); } @@ -410,6 +410,8 @@ class PaginatedPartList extends StatefulWidget { class _PaginatedPartListState extends State { + _PaginatedPartListState(this.filters, this.onTotalChanged); + static const _pageSize = 25; String _searchTerm = ""; @@ -418,8 +420,6 @@ class _PaginatedPartListState extends State { final Map filters; - _PaginatedPartListState(this.filters, this.onTotalChanged); - final PagingController _pagingController = PagingController(firstPageKey: 0); @override diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index 4bea741d..21b9e380 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -10,10 +10,10 @@ import "package:inventree/l10.dart"; class CompanyDetailWidget extends StatefulWidget { - final InvenTreeCompany company; - const CompanyDetailWidget(this.company, {Key? key}) : super(key: key); + final InvenTreeCompany company; + @override _CompanyDetailState createState() => _CompanyDetailState(company); diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 475ed27c..92868971 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -13,10 +13,10 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; class InvenTreeDrawer extends StatelessWidget { - final BuildContext context; - const InvenTreeDrawer(this.context); + final BuildContext context; + void _closeDrawer() { // Close the drawer Navigator.of(context).pop(); diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 6589a89d..95551c2d 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -31,14 +31,14 @@ class InvenTreeHomePage extends StatefulWidget { class _InvenTreeHomePageState extends State { - final GlobalKey<_InvenTreeHomePageState> _homeKey = GlobalKey<_InvenTreeHomePageState>(); - _InvenTreeHomePageState() : super() { // Initially load the profile and attempt server connection _loadProfile(); } + final GlobalKey<_InvenTreeHomePageState> _homeKey = GlobalKey<_InvenTreeHomePageState>(); + // Selected user profile UserProfile? _profile; @@ -266,7 +266,7 @@ class _InvenTreeHomePageState extends State { appBar: AppBar( title: Text(L10().appTitle), ), - drawer: new InvenTreeDrawer(context), + drawer: InvenTreeDrawer(context), body: ListView( physics: ClampingScrollPhysics(), shrinkWrap: true, diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 31ab8c4d..c2670e38 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -31,6 +31,8 @@ class LocationDisplayWidget extends StatefulWidget { class _LocationDisplayState extends RefreshableState { + _LocationDisplayState(this.location); + final InvenTreeStockLocation? location; @override @@ -92,8 +94,6 @@ class _LocationDisplayState extends RefreshableState { ); } - _LocationDisplayState(this.location); - List _sublocations = []; String _locationFilter = ""; @@ -428,10 +428,11 @@ List detailTiles() { class SublocationList extends StatelessWidget { - final List _locations; const SublocationList(this._locations); + final List _locations; + void _openLocation(BuildContext context, int pk) { InvenTreeStockLocation().get(pk).then((var loc) { @@ -475,10 +476,10 @@ class SublocationList extends StatelessWidget { class PaginatedStockList extends StatefulWidget { - final Map filters; - const PaginatedStockList(this.filters); + final Map filters; + @override _PaginatedStockListState createState() => _PaginatedStockListState(filters); } @@ -486,14 +487,14 @@ class PaginatedStockList extends StatefulWidget { class _PaginatedStockListState extends State { + _PaginatedStockListState(this.filters); + static const _pageSize = 25; String _searchTerm = ""; final Map filters; - _PaginatedStockListState(this.filters); - final PagingController _pagingController = PagingController(firstPageKey: 0); @override diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index ad639766..c5482e35 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -5,14 +5,14 @@ import "package:inventree/l10.dart"; class PaginatedSearchWidget extends StatelessWidget { + PaginatedSearchWidget(this.controller, this.onChanged, this.results); + Function onChanged; int results = 0; TextEditingController controller; - PaginatedSearchWidget(this.controller, this.onChanged, this.results); - @override Widget build(BuildContext context) { return ListTile( diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 6a1cdd18..4c4c8761 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -34,10 +34,10 @@ class PartDetailWidget extends StatefulWidget { class _PartDisplayState extends RefreshableState { - InvenTreePart part; - _PartDisplayState(this.part); + InvenTreePart part; + @override String getAppBarTitle(BuildContext context) => L10().partDetails; diff --git a/lib/widget/part_notes.dart b/lib/widget/part_notes.dart index fe4b0348..e2618d2f 100644 --- a/lib/widget/part_notes.dart +++ b/lib/widget/part_notes.dart @@ -10,10 +10,10 @@ import "package:inventree/l10.dart"; class PartNotesWidget extends StatefulWidget { - final InvenTreePart part; - const PartNotesWidget(this.part, {Key? key}) : super(key: key); + final InvenTreePart part; + @override _PartNotesState createState() => _PartNotesState(part); } @@ -21,10 +21,10 @@ class PartNotesWidget extends StatefulWidget { class _PartNotesState extends RefreshableState { - final InvenTreePart part; - _PartNotesState(this.part); + final InvenTreePart part; + @override Future request() async { await part.reload(); diff --git a/lib/widget/search.dart b/lib/widget/search.dart index a4fd9a18..a6a73537 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -17,18 +17,6 @@ import "package:inventree/api.dart"; class PartSearchDelegate extends SearchDelegate { - final partSearchKey = GlobalKey(); - - BuildContext context; - - // What did we search for last time? - String _cachedQuery = ""; - - bool _searching = false; - - // Custom filters for the part search - Map _filters = {}; - PartSearchDelegate(this.context, {Map filters = const {}}) { // Copy filter values @@ -42,6 +30,18 @@ class PartSearchDelegate extends SearchDelegate { } } + final partSearchKey = GlobalKey(); + + BuildContext context; + + // What did we search for last time? + String _cachedQuery = ""; + + bool _searching = false; + + // Custom filters for the part search + Map _filters = {}; + @override String get searchFieldLabel => L10().searchParts; @@ -207,17 +207,6 @@ class PartSearchDelegate extends SearchDelegate { class StockSearchDelegate extends SearchDelegate { - final stockSearchKey = GlobalKey(); - - final BuildContext context; - - String _cachedQuery = ""; - - bool _searching = false; - - // Custom filters for the stock item search - Map _filters = {}; - StockSearchDelegate(this.context, {Map filters = const {}}) { // Copy filter values @@ -231,6 +220,17 @@ class StockSearchDelegate extends SearchDelegate { } } + final stockSearchKey = GlobalKey(); + + final BuildContext context; + + String _cachedQuery = ""; + + bool _searching = false; + + // Custom filters for the stock item search + Map _filters = {}; + @override String get searchFieldLabel => L10().searchStock; diff --git a/lib/widget/spinner.dart b/lib/widget/spinner.dart index 9b53a93e..d5723ccf 100644 --- a/lib/widget/spinner.dart +++ b/lib/widget/spinner.dart @@ -5,9 +5,6 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/app_colors.dart"; class Spinner extends StatefulWidget { - final IconData? icon; - final Duration duration; - final Color color; const Spinner({ this.color = COLOR_GRAY_LIGHT, @@ -16,6 +13,10 @@ class Spinner extends StatefulWidget { this.duration = const Duration(milliseconds: 1800), }) : super(key: key); + final IconData? icon; + final Duration duration; + final Color color; + @override _SpinnerState createState() => _SpinnerState(); } diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index e4431190..7ed69124 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -35,6 +35,8 @@ class StockDetailWidget extends StatefulWidget { class _StockItemDisplayState extends RefreshableState { + _StockItemDisplayState(this.item); + @override String getAppBarTitle(BuildContext context) => L10().stockItem; @@ -46,8 +48,6 @@ class _StockItemDisplayState extends RefreshableState { final _countStockKey = GlobalKey(); final _moveStockKey = GlobalKey(); - _StockItemDisplayState(this.item); - @override List getAppBarActions(BuildContext context) { diff --git a/lib/widget/stock_item_test_results.dart b/lib/widget/stock_item_test_results.dart index 3651fd2c..27556e50 100644 --- a/lib/widget/stock_item_test_results.dart +++ b/lib/widget/stock_item_test_results.dart @@ -26,6 +26,8 @@ class StockItemTestResultsWidget extends StatefulWidget { class _StockItemTestResultDisplayState extends RefreshableState { + _StockItemTestResultDisplayState(this.item); + @override String getAppBarTitle(BuildContext context) => L10().testResults; @@ -49,8 +51,6 @@ class _StockItemTestResultDisplayState extends RefreshableState addTestResult(BuildContext context, {String name = "", bool nameIsEditable = true, bool result = false, String value = "", bool valueRequired = false, bool attachmentRequired = false}) async { InvenTreeStockItemTestResult().createForm( diff --git a/lib/widget/stock_notes.dart b/lib/widget/stock_notes.dart index 0a7bc1b9..28c1c890 100644 --- a/lib/widget/stock_notes.dart +++ b/lib/widget/stock_notes.dart @@ -12,10 +12,10 @@ import "package:inventree/api.dart"; class StockNotesWidget extends StatefulWidget { - final InvenTreeStockItem item; - const StockNotesWidget(this.item, {Key? key}) : super(key: key); + final InvenTreeStockItem item; + @override _StockNotesState createState() => _StockNotesState(item); } @@ -23,10 +23,10 @@ class StockNotesWidget extends StatefulWidget { class _StockNotesState extends RefreshableState { - final InvenTreeStockItem item; - _StockNotesState(this.item); + final InvenTreeStockItem item; + @override String getAppBarTitle(BuildContext context) => L10().stockItemNotes; From be36811cb3dd54062945b0ce04403c87ede0a385 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 21:34:11 +1000 Subject: [PATCH 25/70] More linting again --- analysis_options.yaml | 4 +++ lib/api_form.dart | 8 +++--- lib/inventree/model.dart | 4 +-- lib/inventree/part.dart | 2 +- lib/inventree/stock.dart | 4 +-- lib/settings/login.dart | 24 +++--------------- lib/widget/category_display.dart | 8 +++--- lib/widget/company_list.dart | 10 ++++---- lib/widget/fields.dart | 2 +- lib/widget/location_display.dart | 4 +-- lib/widget/paginator.dart | 8 +++--- lib/widget/part_attachments_widget.dart | 2 +- lib/widget/part_detail.dart | 33 ++++++++++++++----------- lib/widget/purchase_order_list.dart | 4 +-- lib/widget/refreshable_state.dart | 2 +- lib/widget/search.dart | 12 ++++----- lib/widget/snacks.dart | 6 ++++- lib/widget/spinner.dart | 2 +- lib/widget/starred_parts.dart | 2 +- lib/widget/stock_item_test_results.dart | 2 +- 20 files changed, 68 insertions(+), 75 deletions(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index 07b40c28..418cc57b 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -35,6 +35,8 @@ linter: unnecessary_string_interpolations: false + prefer_interpolation_to_compose_strings: false + no_logic_in_create_state: false parameter_assignments: false @@ -59,3 +61,5 @@ linter: # Blindly follow the Flutter code style, which prefers types everywhere always_specify_types: false + + avoid_unnecessary_containers: false diff --git a/lib/api_form.dart b/lib/api_form.dart index 70971a13..f134a806 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -55,7 +55,7 @@ class APIFormField { bool get multiline => (data["multiline"] ?? false) as bool; // Get the "value" as a string (look for "default" if not available) - dynamic get value => (data["value"] ?? data["default"]); + dynamic get value => data["value"] ?? data["default"]; // Get the "default" as a string dynamic get defaultValue => data["default"]; @@ -92,7 +92,7 @@ class APIFormField { } - bool hasErrors() => errorMessages().length > 0; + bool hasErrors() => errorMessages().isNotEmpty; // Return the error message associated with this field List errorMessages() { @@ -660,7 +660,7 @@ Future launchApiForm(BuildContext context, String title, String url, Map fields; - Function(Map)? onSuccess; + final Function(Map)? onSuccess; @override _APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, method, onSuccess, fileField); diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index bd1517ec..4eb234ec 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -49,7 +49,7 @@ class InvenTreeModel { // Override the web URL for each subclass // Note: If the WEB_URL is the same (except for /api/) as URL then just leave blank - String WEB_URL = ""; + String get WEB_URL => ""; String get webUrl { @@ -118,7 +118,7 @@ class InvenTreeModel { Map jsondata = {}; // Accessor for the API - var api = InvenTreeAPI(); + InvenTreeAPI api = InvenTreeAPI(); int get pk => (jsondata["pk"] ?? -1) as int; diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 804615e6..16b2562d 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -42,7 +42,7 @@ class InvenTreePartCategory extends InvenTreeModel { // TODO - Drive the refactor tractor through this List psplit = pathstring.split("/"); - if (psplit.length > 0) { + if (psplit.isNotEmpty) { psplit.removeLast(); } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index cbfe7142..82c37711 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -114,7 +114,7 @@ class InvenTreeStockItem extends InvenTreeModel { String get URL => "stock/"; @override - String WEB_URL = "stock/item/"; + String get WEB_URL => "stock/item/"; @override Map formFields() { @@ -549,7 +549,7 @@ class InvenTreeStockLocation extends InvenTreeModel { // TODO - Drive the refactor tractor through this List psplit = pathstring.split("/"); - if (psplit.length > 0) { + if (psplit.isNotEmpty) { psplit.removeLast(); } diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 32cdc35c..086be345 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -79,24 +79,6 @@ class _InvenTreeLoginSettingsState extends State { } } - Future _updateProfile(UserProfile? profile) async { - - if (profile == null) { - return; - } - - _reload(); - - if (InvenTreeAPI().isConnected() && InvenTreeAPI().profile != null && profile.key == (InvenTreeAPI().profile?.key ?? "")) { - // Attempt server login (this will load the newly selected profile - - InvenTreeAPI().connectToServer().then((result) { - _reload(); - }); - } - } - - Widget? _getProfileIcon(UserProfile profile) { // Not selected? No icon for you! @@ -134,7 +116,7 @@ class _InvenTreeLoginSettingsState extends State { List children = []; - if (profiles.length > 0) { + if (profiles.isNotEmpty) { for (int idx = 0; idx < profiles.length; idx++) { UserProfile profile = profiles[idx]; @@ -237,9 +219,9 @@ class _InvenTreeLoginSettingsState extends State { class ProfileEditWidget extends StatefulWidget { - ProfileEditWidget(this.profile) : super(); + const ProfileEditWidget(this.profile) : super(); - UserProfile? profile; + final UserProfile? profile; @override _ProfileEditState createState() => _ProfileEditState(profile); diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 80ce8605..3fa0d7d5 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -198,7 +198,7 @@ class _CategoryDisplayState extends RefreshableState { if (loading) { tiles.add(progressIndicator()); - } else if (_subcategories.length == 0) { + } else if (_subcategories.isEmpty) { tiles.add(ListTile( title: Text(L10().noSubcategories), subtitle: Text( @@ -301,7 +301,7 @@ class _CategoryDisplayState extends RefreshableState { } } - if (tiles.length == 0) { + if (tiles.isEmpty) { tiles.add( ListTile( title: Text( @@ -397,11 +397,11 @@ class SubcategoryList extends StatelessWidget { class PaginatedPartList extends StatefulWidget { - PaginatedPartList(this.filters, {this.onTotalChanged}); + const PaginatedPartList(this.filters, {this.onTotalChanged}); final Map filters; - Function(int)? onTotalChanged; + final Function(int)? onTotalChanged; @override _PaginatedPartListState createState() => _PaginatedPartListState(filters, onTotalChanged); diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index acf9b3f8..948cf25a 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -15,11 +15,11 @@ import "package:inventree/l10.dart"; class CompanyListWidget extends StatefulWidget { - CompanyListWidget(this.title, this.filters, {Key? key}) : super(key: key); + const CompanyListWidget(this.title, this.filters, {Key? key}) : super(key: key); - String title; + final String title; - Map filters; + final Map filters; @override _CompanyListWidgetState createState() => _CompanyListWidgetState(title, filters); @@ -49,11 +49,11 @@ class _CompanyListWidgetState extends RefreshableState { class PaginatedCompanyList extends StatefulWidget { - PaginatedCompanyList(this.filters, {this.onTotalChanged}); + const PaginatedCompanyList(this.filters, {this.onTotalChanged}); final Map filters; - Function(int)? onTotalChanged; + final Function(int)? onTotalChanged; @override _CompanyListState createState() => _CompanyListState(filters, onTotalChanged); diff --git a/lib/widget/fields.dart b/lib/widget/fields.dart index 7c6a74d6..eb5b7b1a 100644 --- a/lib/widget/fields.dart +++ b/lib/widget/fields.dart @@ -194,7 +194,7 @@ class StringField extends TextFormField { */ class QuantityField extends TextFormField { - QuantityField({String label = "", String hint = "", String initial = "", double? max, TextEditingController? controller}) : + QuantityField({String label = "", String hint = "", double? max, TextEditingController? controller}) : super( decoration: InputDecoration( labelText: label, diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index c2670e38..145e9982 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -313,13 +313,13 @@ List detailTiles() { L10().sublocations, style: TextStyle(fontWeight: FontWeight.bold), ), - trailing: sublocations.length > 0 ? Text("${sublocations.length}") : null, + trailing: sublocations.isNotEmpty ? Text("${sublocations.length}") : null, ), ]; if (loading) { tiles.add(progressIndicator()); - } else if (_sublocations.length > 0) { + } else if (_sublocations.isNotEmpty) { tiles.add(SublocationList(_sublocations)); } else { tiles.add(ListTile( diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index c5482e35..6d5eba0c 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -5,13 +5,13 @@ import "package:inventree/l10.dart"; class PaginatedSearchWidget extends StatelessWidget { - PaginatedSearchWidget(this.controller, this.onChanged, this.results); + const PaginatedSearchWidget(this.controller, this.onChanged, this.results); - Function onChanged; + final Function onChanged; - int results = 0; + final int results; - TextEditingController controller; + final TextEditingController controller; @override Widget build(BuildContext context) { diff --git a/lib/widget/part_attachments_widget.dart b/lib/widget/part_attachments_widget.dart index 3af0e004..2e14141f 100644 --- a/lib/widget/part_attachments_widget.dart +++ b/lib/widget/part_attachments_widget.dart @@ -123,7 +123,7 @@ class _PartAttachmentDisplayState extends RefreshableState { ); // TODO - Add this action back in once implemented - if (false) { - tiles.add( - ListTile( - title: Text(L10().barcodeScanItem), - leading: FaIcon(FontAwesomeIcons.box), - trailing: FaIcon(FontAwesomeIcons.qrcode), - onTap: () { - // TODO - }, - ), - ); - } - - if (false && !part.isActive && InvenTreeAPI().checkPermission("part", "delete")) { + /* + tiles.add( + ListTile( + title: Text(L10().barcodeScanItem), + leading: FaIcon(FontAwesomeIcons.box), + trailing: FaIcon(FontAwesomeIcons.qrcode), + onTap: () { + // TODO + }, + ), + ); + */ + + /* + // TODO: Implement part deletion + if (!part.isActive && InvenTreeAPI().checkPermission("part", "delete")) { tiles.add( ListTile( title: Text(L10().deletePart), @@ -467,6 +469,7 @@ class _PartDisplayState extends RefreshableState { ) ); } + */ return tiles; } diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index d05010e4..67db6512 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -43,11 +43,11 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters; - Function(int)? onTotalChanged; + final Function(int)? onTotalChanged; @override _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters, onTotalChanged); diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index 77508f3b..fb1e3559 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -9,7 +9,7 @@ abstract class RefreshableState extends State { final refreshableKey = GlobalKey(); // Storage for context once "Build" is called - BuildContext? _context; + late BuildContext? _context; // Current tab index (used for widgets which display bottom tabs) int tabIndex = 0; diff --git a/lib/widget/search.dart b/lib/widget/search.dart index a6a73537..4f2cbd23 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -86,7 +86,7 @@ class PartSearchDelegate extends SearchDelegate { showSnackIcon( "${partResults.length} ${L10().results}", - success: partResults.length > 0, + success: partResults.isNotEmpty, icon: FontAwesomeIcons.pollH, ); @@ -161,7 +161,7 @@ class PartSearchDelegate extends SearchDelegate { search(context); - if (query.length == 0) { + if (query.isEmpty) { return ListTile( title: Text(L10().queryEnter) ); @@ -174,7 +174,7 @@ class PartSearchDelegate extends SearchDelegate { ); } - if (partResults.length == 0) { + if (partResults.isEmpty) { return ListTile( title: Text(L10().noResults), subtitle: Text(L10().queryNoResults + " '${query}'") @@ -275,7 +275,7 @@ class StockSearchDelegate extends SearchDelegate { showSnackIcon( "${itemResults.length} ${L10().results}", - success: itemResults.length > 0, + success: itemResults.isNotEmpty, icon: FontAwesomeIcons.pollH, ); @@ -349,7 +349,7 @@ class StockSearchDelegate extends SearchDelegate { search(context); - if (query.length == 0) { + if (query.isEmpty) { return ListTile( title: Text(L10().queryEnter) ); @@ -362,7 +362,7 @@ class StockSearchDelegate extends SearchDelegate { ); } - if (itemResults.length == 0) { + if (itemResults.isEmpty) { return ListTile( title: Text(L10().noResults), subtitle: Text(L10().queryNoResults + " '${query}'") diff --git a/lib/widget/snacks.dart b/lib/widget/snacks.dart index 6a33cee1..fdbeb1c0 100644 --- a/lib/widget/snacks.dart +++ b/lib/widget/snacks.dart @@ -17,7 +17,11 @@ import "package:inventree/l10.dart"; void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) { - OneContext().hideCurrentSnackBar(); + BuildContext? context = OneContext().context; + + if (context != null) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + } Color backgroundColor = Colors.deepOrange; diff --git a/lib/widget/spinner.dart b/lib/widget/spinner.dart index d5723ccf..faebe750 100644 --- a/lib/widget/spinner.dart +++ b/lib/widget/spinner.dart @@ -22,7 +22,7 @@ class Spinner extends StatefulWidget { } class _SpinnerState extends State with SingleTickerProviderStateMixin { - AnimationController? _controller; + late AnimationController? _controller; Widget? _child; @override diff --git a/lib/widget/starred_parts.dart b/lib/widget/starred_parts.dart index 6d15d952..fbb33936 100644 --- a/lib/widget/starred_parts.dart +++ b/lib/widget/starred_parts.dart @@ -71,7 +71,7 @@ class _StarredPartState extends RefreshableState { return progressIndicator(); } - if (starredParts.length == 0) { + if (starredParts.isEmpty) { return ListView( children: [ ListTile( diff --git a/lib/widget/stock_item_test_results.dart b/lib/widget/stock_item_test_results.dart index 27556e50..d7d3a7cf 100644 --- a/lib/widget/stock_item_test_results.dart +++ b/lib/widget/stock_item_test_results.dart @@ -142,7 +142,7 @@ class _StockItemTestResultDisplayState extends RefreshableState Date: Tue, 28 Sep 2021 23:06:35 +1000 Subject: [PATCH 26/70] Refactor "home" screen - Add serttings button - Simplify server connection display --- lib/widget/drawer.dart | 3 +- lib/widget/home.dart | 339 ++++++++++++++--------------------------- 2 files changed, 120 insertions(+), 222 deletions(-) diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 92868971..93a9cdf7 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -1,6 +1,5 @@ import "package:inventree/api.dart"; import "package:inventree/barcode.dart"; -import "package:inventree/widget/company_list.dart"; import "package:inventree/widget/search.dart"; import "package:flutter/material.dart"; import "package:inventree/l10.dart"; @@ -76,6 +75,7 @@ class InvenTreeDrawer extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } + /* void _showSuppliers() { if (!InvenTreeAPI().checkConnection(context)) return; _closeDrawer(); @@ -96,6 +96,7 @@ class InvenTreeDrawer extends StatelessWidget { Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); } + */ /* * Load settings widget diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 95551c2d..38623ff6 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -1,4 +1,5 @@ import "package:inventree/app_colors.dart"; +import "package:inventree/settings/settings.dart"; import "package:inventree/user_profile.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; @@ -16,9 +17,7 @@ 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/purchase_order_list.dart"; -import "package:inventree/widget/search.dart"; import "package:inventree/widget/snacks.dart"; -import "package:inventree/widget/spinner.dart"; import "package:inventree/widget/drawer.dart"; class InvenTreeHomePage extends StatefulWidget { @@ -42,22 +41,8 @@ class _InvenTreeHomePageState extends State { // Selected user profile UserProfile? _profile; - void _searchParts() { - if (!InvenTreeAPI().checkConnection(context)) return; - - showSearch( - context: context, - delegate: PartSearchDelegate(context) - ); - } - - void _searchStock() { - if (!InvenTreeAPI().checkConnection(context)) return; - - showSearch( - context: context, - delegate: StockSearchDelegate(context) - ); + void _search(BuildContext context) { + // TODO } void _scan(BuildContext context) { @@ -72,12 +57,18 @@ class _InvenTreeHomePageState extends State { Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); } + void _showSettings(BuildContext context) { + Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget())); + } + + /* void _showStarredParts(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; // TODO // Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); } + */ void _showStock(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; @@ -143,67 +134,6 @@ class _InvenTreeHomePageState extends State { setState(() {}); } - ListTile _serverTile() { - - // No profile selected - // Tap to select / create a profile - if (_profile == null) { - return ListTile( - title: Text(L10().profileNotSelected), - subtitle: Text(L10().profileTapToCreate), - leading: FaIcon(FontAwesomeIcons.server), - trailing: FaIcon( - FontAwesomeIcons.user, - color: COLOR_DANGER, - ), - onTap: () { - _selectProfile(); - }, - ); - } - - // Profile is selected ... - if (InvenTreeAPI().isConnecting()) { - return ListTile( - title: Text(L10().serverConnecting), - subtitle: Text("${InvenTreeAPI().baseUrl}"), - leading: FaIcon(FontAwesomeIcons.server), - trailing: Spinner( - icon: FontAwesomeIcons.spinner, - color: COLOR_PROGRESS, - ), - onTap: () { - _selectProfile(); - } - ); - } else if (InvenTreeAPI().isConnected()) { - return ListTile( - title: Text(L10().serverConnected), - subtitle: Text("${InvenTreeAPI().baseUrl}"), - leading: FaIcon(FontAwesomeIcons.server), - trailing: FaIcon( - FontAwesomeIcons.checkCircle, - color: COLOR_SUCCESS - ), - onTap: () { - _selectProfile(); - }, - ); - } else { - return ListTile( - title: Text(L10().serverCouldNotConnect), - subtitle: Text("${_profile!.server}"), - leading: FaIcon(FontAwesomeIcons.server), - trailing: FaIcon( - FontAwesomeIcons.timesCircle, - color: COLOR_DANGER, - ), - onTap: () { - _selectProfile(); - }, - ); - } - } Widget _iconButton(BuildContext context, String label, IconData icon, {Function()? callback, String role = "", String permission = ""}) { @@ -230,6 +160,7 @@ class _InvenTreeHomePageState extends State { ), Divider( height: 10, + thickness: 0, ), Text( label, @@ -257,52 +188,35 @@ class _InvenTreeHomePageState extends State { ); } - - @override - Widget build(BuildContext context) { - - return Scaffold( - key: _homeKey, - appBar: AppBar( - title: Text(L10().appTitle), + List getGridTiles(BuildContext context) { + return [ + _iconButton( + context, + L10().scanBarcode, + FontAwesomeIcons.barcode, + callback: () { + _scan(context); + } + ), + _iconButton( + context, + L10().search, + FontAwesomeIcons.search, + callback: () { + _search(context); + } + ), + _iconButton( + context, + L10().parts, + FontAwesomeIcons.shapes, + callback: () { + _showParts(context); + } ), - drawer: InvenTreeDrawer(context), - body: ListView( - physics: ClampingScrollPhysics(), - shrinkWrap: true, - children: [ - GridView.extent( - maxCrossAxisExtent: 140, - shrinkWrap: true, - physics: ClampingScrollPhysics(), - children: [ - _iconButton( - context, - L10().scanBarcode, - FontAwesomeIcons.barcode, - callback: () { - _scan(context); - } - ), - _iconButton( - context, - L10().search, - FontAwesomeIcons.search, - callback: () { - // TODO: Launch "generic" search widget - } - ), - _iconButton( - context, - L10().parts, - FontAwesomeIcons.shapes, - callback: () { - _showParts(context); - } - ), - // TODO - Re-add starred parts link - /* + // TODO - Re-add starred parts link + /* Column( children: [ IconButton( @@ -316,106 +230,89 @@ class _InvenTreeHomePageState extends State { ), */ - _iconButton( - context, - L10().stock, - FontAwesomeIcons.boxes, - callback: () { - _showStock(context); - } + _iconButton( + context, + L10().stock, + FontAwesomeIcons.boxes, + callback: () { + _showStock(context); + } + ), + _iconButton( + context, + L10().purchaseOrders, + FontAwesomeIcons.shoppingCart, + callback: () { + _showPurchaseOrders(context); + } + ), + _iconButton( + context, + L10().salesOrders, + FontAwesomeIcons.truck, + ), + _iconButton( + context, + L10().suppliers, + FontAwesomeIcons.building, + callback: () { + _showSuppliers(context); + } + ), + _iconButton( + context, + L10().manufacturers, + FontAwesomeIcons.industry, + callback: () { + _showManufacturers(context); + } + ), + _iconButton( + context, + L10().customers, + FontAwesomeIcons.userTie, + callback: () { + _showCustomers(context); + } + ), + _iconButton( + context, + L10().settings, + FontAwesomeIcons.cogs, + callback: () { + _showSettings(context); + } + ) + ]; + } + + @override + Widget build(BuildContext context) { + + return Scaffold( + key: _homeKey, + appBar: AppBar( + title: Text(L10().appTitle), + actions: [ + IconButton( + icon: FaIcon( + FontAwesomeIcons.server, + color: InvenTreeAPI().isConnected() ? COLOR_SUCCESS : COLOR_DANGER, ), - _iconButton( - context, - L10().purchaseOrders, - FontAwesomeIcons.shoppingCart, - callback: () { - _showPurchaseOrders(context); - } - ), - _iconButton( - context, - L10().salesOrders, - FontAwesomeIcons.truck, - ), - _iconButton( - context, - L10().suppliers, - FontAwesomeIcons.building, - callback: () { - _showSuppliers(context); - } - ), - _iconButton( - context, - L10().manufacturers, - FontAwesomeIcons.industry, - callback: () { - _showManufacturers(context); - } - ), - _iconButton( - context, - L10().customers, - FontAwesomeIcons.userTie, - callback: () { - _showCustomers(context); - } - ), - /* - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.tools), - tooltip: "Build", - onPressed: _unsupported, - ), - Text("Build"), - ], - ), - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.shoppingCart), - tooltip: "Order", - onPressed: _unsupported, - ), - Text("Order"), - ] - ), - Column( - children: [ - IconButton( - icon: new FaIcon(FontAwesomeIcons.truck), - tooltip: "Ship", - onPressed: _unsupported, - ), - Text("Ship"), - ] - ) - ], - ), - Spacer(), - */ - /* - Spacer(), - Row( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: _serverTile(), - ), - ], - ), - ]), - */ - ], + onPressed: _selectProfile, ) - ] + ], + ), + drawer: InvenTreeDrawer(context), + body: ListView( + children: [ + GridView.extent( + maxCrossAxisExtent: 140, + shrinkWrap: true, + physics: ClampingScrollPhysics(), + children: getGridTiles(context), + ), + ], ), ); } From 04344c6c8b1b1dbd607bf4d6246ecbc130b4d09b Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 23:26:10 +1000 Subject: [PATCH 27/70] Add a dummy .dsn file --- .github/workflows/lint.yaml | 33 ---------------- .github/workflows/test.yaml | 77 +++++++++++++++++++++++++++++++++++++ .gitignore | 2 + lib/dummy_dsn.dart | 3 ++ 4 files changed, 82 insertions(+), 33 deletions(-) delete mode 100644 .github/workflows/lint.yaml create mode 100644 .github/workflows/test.yaml create mode 100644 lib/dummy_dsn.dart diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml deleted file mode 100644 index 2233262c..00000000 --- a/.github/workflows/lint.yaml +++ /dev/null @@ -1,33 +0,0 @@ -# Run flutter linting checks - -name: linting - -on: - push: - branches: - - master - pull_request: - branches: - - master - -jobs: - - lint: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Setup Java - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - flutter-version: '2.2.3' - - run: flutter pub get - - run: flutter analyze - \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 00000000..e9a7030b --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,77 @@ +# Run flutter linting checks + +name: test + +on: + push: + branches: + - master + pull_request: + branches: + - master + +jobs: + + lint: + runs-on: ubuntu-latest + + env: + SENTRY_DSN: ${{ secrets.SENTRY_DSN }} + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + flutter-version: '2.2.3' + - run: flutter pub get + - run: cp lib/dummy_dsn.dart lib/dsn.dart + - run: flutter analyze + - run: flutter test --coverage + + android: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + flutter-version: '2.2.3' + - run: flutter pub get + - run: cp lib/dummy_dsn.dart lib/dsn.dart + - run: flutter build apk + +ios: + runs-on: macos-latest + + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + flutter-version: '2.2.3' + - run: flutter pub get + - run: cp lib/dummy_dsn.dart lib/dsn.dart + - run: flutter build ios --release --no-codesign diff --git a/.gitignore b/.gitignore index 6c9c3b53..2604e33b 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ .history .svn/ +coverage/* + # Sentry API key lib/dsn.dart diff --git a/lib/dummy_dsn.dart b/lib/dummy_dsn.dart new file mode 100644 index 00000000..d53bb6ff --- /dev/null +++ b/lib/dummy_dsn.dart @@ -0,0 +1,3 @@ +// Dummy DSN to use for unit testing, etc + +const String SENTRY_DSN_KEY = "https://12345678901234567890@abcdef.ingest.sentry.io/11223344"; \ No newline at end of file From 298ba27e41d5371623ef8114e02e12dda2c7abd2 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 23:27:09 +1000 Subject: [PATCH 28/70] Fix workflow file --- .github/workflows/test.yaml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e9a7030b..453850b4 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -56,22 +56,22 @@ jobs: - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk -ios: - runs-on: macos-latest + ios: + runs-on: macos-latest - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Setup Java - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - flutter-version: '2.2.3' - - run: flutter pub get - - run: cp lib/dummy_dsn.dart lib/dsn.dart - - run: flutter build ios --release --no-codesign + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: recursive + - name: Setup Java + uses: actions/setup-java@v1 + with: + java-version: '12.x' + - name: Setup Flutter + uses: subosito/flutter-action@v1 + with: + flutter-version: '2.2.3' + - run: flutter pub get + - run: cp lib/dummy_dsn.dart lib/dsn.dart + - run: flutter build ios --release --no-codesign From a9b96f22a38def5a4754e7c8b325270d98732495 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 28 Sep 2021 23:53:26 +1000 Subject: [PATCH 29/70] Fixes for purchase order editing --- .github/workflows/test.yaml | 2 +- lib/api_form.dart | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 453850b4..8b1950b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -47,7 +47,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v1 with: - java-version: '12.x' + java-version: '8.x' - name: Setup Flutter uses: subosito/flutter-action@v1 with: diff --git a/lib/api_form.dart b/lib/api_form.dart index f134a806..5da5eff3 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -194,7 +194,7 @@ class APIFormField { labelStyle: _labelStyle(), ), initialValue: DateTime.tryParse((value ?? "") as String), - autovalidateMode: AutovalidateMode.always, + autovalidateMode: AutovalidateMode.disabled, validator: (e) { // TODO }, @@ -600,8 +600,8 @@ Future launchApiForm(BuildContext context, String title, String url, Map remoteField = (availableFields[fieldName] ?? {}) as Map; - Map localField = (fields[fieldName] ?? {}) as Map; + final remoteField = Map.from(availableFields[fieldName] as Map); + final localField = Map.from(fields[fieldName] as Map); // Override defined field parameters, if provided for (String key in localField.keys) { From 94cca19ccc1d1f5e0a5b65fdb39f229fffec46a8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 00:10:07 +1000 Subject: [PATCH 30/70] Bug fix for API forms --- lib/api_form.dart | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 5da5eff3..95dad26f 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -625,14 +625,8 @@ Future launchApiForm(BuildContext context, String title, String url, Map Date: Wed, 29 Sep 2021 00:18:00 +1000 Subject: [PATCH 31/70] try building on macos --- .github/workflows/test.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 8b1950b7..fdd0c706 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -37,7 +37,7 @@ jobs: - run: flutter test --coverage android: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout code @@ -47,7 +47,7 @@ jobs: - name: Setup Java uses: actions/setup-java@v1 with: - java-version: '8.x' + java-version: '12.x' - name: Setup Flutter uses: subosito/flutter-action@v1 with: From cbb668687e928d1ac8212492f7f01fa3e1465a41 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 00:24:21 +1000 Subject: [PATCH 32/70] Set default location for part - Used as "default" when creating new StockItem for that part --- lib/api_form.dart | 2 +- lib/inventree/part.dart | 8 +++++--- lib/widget/part_detail.dart | 6 ++++++ 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 95dad26f..32870769 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -395,7 +395,7 @@ class APIFormField { // Render a "related field" based on the "model" type // Convert to JSON - Map data = item as Map; + var data = Map.from((item ?? {}) as Map); switch (model) { case "part": diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 16b2562d..e327cb9c 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -139,9 +139,9 @@ class InvenTreePart extends InvenTreeModel { "keywords": {}, "link": {}, - // Parent category - "category": { - }, + "category": {}, + + "default_location": {}, "units": {}, @@ -242,6 +242,8 @@ class InvenTreePart extends InvenTreeModel { }); } + int? get defaultLocation => jsondata["default_location"] as int?; + // Get the number of stock on order for this Part double get onOrder => double.tryParse(jsondata["ordering"].toString()) ?? 0; diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index e9ce5ec4..54d2e4c5 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -400,6 +400,12 @@ class _PartDisplayState extends RefreshableState { fields["part"]["hidden"] = true; + int? default_location = part.defaultLocation; + + if (default_location != null) { + fields["location"]["value"] = default_location; + } + InvenTreeStockItem().createForm( context, L10().stockItemCreate, From 961a35d41097374e2d58f963ead99026da5192bc Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 00:30:09 +1000 Subject: [PATCH 33/70] bug fix for "owner" rendering --- lib/api_form.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 32870769..c099300a 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -443,8 +443,8 @@ class APIFormField { ) : null, ); case "owner": - String name = (item["name"] ?? "") as String; - bool isGroup = (item["label"] ?? "") == "group"; + String name = (data["name"] ?? "") as String; + bool isGroup = (data["label"] ?? "") == "group"; return ListTile( title: Text(name), leading: FaIcon(isGroup ? FontAwesomeIcons.users : FontAwesomeIcons.user), From 46ecb8ebf6390db1c7dbf88d587fd24e9047a13f Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 00:39:55 +1000 Subject: [PATCH 34/70] Install specific gradle version? --- .github/workflows/test.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index fdd0c706..29473262 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -52,7 +52,11 @@ jobs: uses: subosito/flutter-action@v1 with: flutter-version: '2.2.3' - - run: flutter pub get + - name: Install Gradle + run: | + brew install gradle + gradle wrapper --gradle-version 6.7.1 + - run: flutter pub get - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk From f21cbe3b1c772872bb2fb7b7bdca5a0ec997bc43 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 09:04:04 +1000 Subject: [PATCH 35/70] Check gradle version --- .github/workflows/test.yaml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 29473262..b09ed9c0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -52,11 +52,9 @@ jobs: uses: subosito/flutter-action@v1 with: flutter-version: '2.2.3' - - name: Install Gradle - run: | - brew install gradle - gradle wrapper --gradle-version 6.7.1 - - run: flutter pub get + - name: Check Gradle Version + run: cd android && ./gradlew --version + - run: flutter pub get - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk From 5f657a236f4141743d83f9fac232b9a3e807883b Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 09:15:35 +1000 Subject: [PATCH 36/70] run pub get first --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b09ed9c0..07496e4b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -52,9 +52,9 @@ jobs: uses: subosito/flutter-action@v1 with: flutter-version: '2.2.3' + - run: flutter pub get - name: Check Gradle Version run: cd android && ./gradlew --version - - run: flutter pub get - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk From 226b655723bbfb0d3f14833bf0ca0fd643bbb683 Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 09:23:56 +1000 Subject: [PATCH 37/70] Add gradle installation step --- .github/workflows/test.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 07496e4b..1f2fd2b7 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -52,9 +52,11 @@ jobs: uses: subosito/flutter-action@v1 with: flutter-version: '2.2.3' + - name: Setup Gradle + uses: gradle/gradle-build-action@v2 + with: + gradle-version: 6.7.1 - run: flutter pub get - - name: Check Gradle Version - run: cd android && ./gradlew --version - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk From 991e2a80a51318a8331c009b16c752fa5fa9a8ac Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 09:52:43 +1000 Subject: [PATCH 38/70] Update ext.kotlin_version Ref: https://stackoverflow.com/questions/68967375/gradle-build-failed-execution-failed-for-task-audioplayerscompiledebugkotlin --- android/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index c5962208..b20d18e2 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = '1.5.10' + ext.kotlin_version = '1.5.30' repositories { google() From a304ee9a24ee8ebaad0157f0e6cb226f9b9fbe8e Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 11:12:49 +1000 Subject: [PATCH 39/70] Different versions of stuffs --- android/build.gradle | 4 ++-- android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index b20d18e2..13b686e5 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = '1.5.30' + ext.kotlin_version = '1.5.10' repositories { google() @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' + classpath 'com.android.tools.build:gradle:4.1.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 8b795a6c..800a7627 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip \ No newline at end of file From 56d66424b6d3d64eba585e8dafb425b9e7dbadcf Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 13:53:35 +1000 Subject: [PATCH 40/70] Downgrade to 6.6.1 - Ref: https://developer.android.com/studio/releases/gradle-plugin --- android/build.gradle | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 13b686e5..af27b36c 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' + classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 800a7627..9b9d49f3 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip \ No newline at end of file +distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip \ No newline at end of file From 414bbb54439b90e9d81c75e94d036813fd2db92e Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 15:20:36 +1000 Subject: [PATCH 41/70] Specify gradle version --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1f2fd2b7..e84831f1 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -55,7 +55,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2 with: - gradle-version: 6.7.1 + gradle-version: 6.1.1 - run: flutter pub get - run: cp lib/dummy_dsn.dart lib/dsn.dart - run: flutter build apk From d433e6327bf7a12a65e57eed025adf5671e4086e Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 29 Sep 2021 17:36:09 +1000 Subject: [PATCH 42/70] Disable build steps --- .github/workflows/test.yaml | 84 ++++++++++++++++++------------------- 1 file changed, 42 insertions(+), 42 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index e84831f1..9221e66b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -36,46 +36,46 @@ jobs: - run: flutter analyze - run: flutter test --coverage - android: - runs-on: macos-latest + #android: + # runs-on: macos-latest + # + # steps: + # - name: Checkout code + # uses: actions/checkout@v2 + # with: + # submodules: recursive + # - name: Setup Java + # uses: actions/setup-java@v1 + # with: + # java-version: '12.x' + # - name: Setup Flutter + # uses: subosito/flutter-action@v1 + # with: + # flutter-version: '2.2.3' + # - name: Setup Gradle + # uses: gradle/gradle-build-action@v2 + # with: + # gradle-version: 6.1.1 + # - run: flutter pub get + # - run: cp lib/dummy_dsn.dart lib/dsn.dart + # - run: flutter build apk - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Setup Java - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - flutter-version: '2.2.3' - - name: Setup Gradle - uses: gradle/gradle-build-action@v2 - with: - gradle-version: 6.1.1 - - run: flutter pub get - - run: cp lib/dummy_dsn.dart lib/dsn.dart - - run: flutter build apk - - ios: - runs-on: macos-latest - - steps: - - name: Checkout code - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Setup Java - uses: actions/setup-java@v1 - with: - java-version: '12.x' - - name: Setup Flutter - uses: subosito/flutter-action@v1 - with: - flutter-version: '2.2.3' - - run: flutter pub get - - run: cp lib/dummy_dsn.dart lib/dsn.dart - - run: flutter build ios --release --no-codesign + #ios: + # runs-on: macos-latest + # + # steps: + # - name: Checkout code + # uses: actions/checkout@v2 + # with: + # submodules: recursive + # - name: Setup Java + # uses: actions/setup-java@v1 + # with: + # java-version: '12.x' + # - name: Setup Flutter + # uses: subosito/flutter-action@v1 + # with: + # flutter-version: '2.2.3' + # - run: flutter pub get + # - run: cp lib/dummy_dsn.dart lib/dsn.dart + # - run: flutter build ios --release --no-codesign From 86584b366f68c1830762b899ff42eb5217bd6b88 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 2 Oct 2021 11:53:45 +1000 Subject: [PATCH 43/70] Building context menu for purchase order line item --- lib/api.dart | 6 ++++ lib/l10n | 2 +- lib/widget/home.dart | 2 ++ lib/widget/purchase_order_detail.dart | 42 ++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/lib/api.dart b/lib/api.dart index 96202c3d..64993608 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -244,6 +244,12 @@ class InvenTreeAPI { // Ensure we only ever create a single instance of the API class static final InvenTreeAPI _api = InvenTreeAPI._internal(); + bool supportPoReceive() { + + // API endpoint for receiving purchase order line items was introduced in v12 + return _apiVersion >= 12; + } + /* * Connect to the remote InvenTree server: * diff --git a/lib/l10n b/lib/l10n index ed69d6ef..bdaf7a51 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit ed69d6efe7672770ee27f4d1b4fc8ab7793266da +Subproject commit bdaf7a516cc40caf6ee1110fea6af073a61d7829 diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 38623ff6..eba54768 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -246,11 +246,13 @@ class _InvenTreeHomePageState extends State { _showPurchaseOrders(context); } ), + /* _iconButton( context, L10().salesOrders, FontAwesomeIcons.truck, ), + */ _iconButton( context, L10().suppliers, diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 41bbaeb8..b89117d9 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -172,6 +172,43 @@ class _PurchaseOrderDetailState extends RefreshableState children = []; + + if (InvenTreeAPI().supportPoReceive()) { + children.add( + SimpleDialogOption( + onPressed: () { + + }, + child: ListTile( + title: Text(L10().receiveItem), + leading: FaIcon(FontAwesomeIcons.signInAlt), + ) + ) + ); + } + + // No valid actions available + if (children.isEmpty) { + return; + } + + children.insert(0, Divider()); + + showDialog( + context: context, + builder: (BuildContext context) { + return SimpleDialog( + title: Text(L10().lineItem), + children: children, + ); + } + ); + + } + List lineTiles(BuildContext context) { List tiles = []; @@ -190,7 +227,10 @@ class _PurchaseOrderDetailState extends RefreshableState Date: Sat, 2 Oct 2021 22:31:28 +1000 Subject: [PATCH 44/70] API_Form: Allow customizable icon - Also fixes for API search --- lib/api_form.dart | 50 ++++++++++++++++++++++--------- lib/inventree/model.dart | 16 +++++++--- lib/inventree/purchase_order.dart | 2 ++ lib/widget/search.dart | 5 ++-- lib/widget/stock_detail.dart | 8 +---- 5 files changed, 53 insertions(+), 28 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index c099300a..50e44d54 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -559,7 +559,16 @@ Map extractFields(APIResponse response) { * @param method is the HTTP method to use to send the form data to the server (e.g. POST / PATCH) */ -Future launchApiForm(BuildContext context, String title, String url, Map fields, {String fileField = "", Map modelData = const {}, String method = "PATCH", Function(Map)? onSuccess, Function? onCancel}) async { +Future launchApiForm( + BuildContext context, String title, String url, Map fields, + { + String fileField = "", + Map modelData = const {}, + String method = "PATCH", + Function(Map)? onSuccess, + Function? onCancel, + IconData icon = FontAwesomeIcons.save, + }) async { var options = await InvenTreeAPI().options(url); @@ -641,12 +650,13 @@ Future launchApiForm(BuildContext context, String title, String url, Map APIFormWidget( - title, - url, - formFields, - method, - onSuccess: onSuccess, - fileField: fileField, + title, + url, + formFields, + method, + onSuccess: onSuccess, + fileField: fileField, + icon: icon, )) ); } @@ -663,6 +673,7 @@ class APIFormWidget extends StatefulWidget { Key? key, this.onSuccess, this.fileField = "", + this.icon = FontAwesomeIcons.save, } ) : super(key: key); @@ -677,29 +688,34 @@ class APIFormWidget extends StatefulWidget { final String fileField; + // Icon + final IconData icon; + final List fields; final Function(Map)? onSuccess; @override - _APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, method, onSuccess, fileField); + _APIFormWidgetState createState() => _APIFormWidgetState(title, url, fields, method, onSuccess, fileField, icon); } class _APIFormWidgetState extends State { - _APIFormWidgetState(this.title, this.url, this.fields, this.method, this.onSuccess, this.fileField) : super(); + _APIFormWidgetState(this.title, this.url, this.fields, this.method, this.onSuccess, this.fileField, this.icon) : super(); final _formKey = GlobalKey(); - String title; + final String title; - String url; + final String url; - String method; + final String method; - String fileField; + final String fileField; + + final IconData icon; List fields; @@ -878,6 +894,12 @@ class _APIFormWidgetState extends State { field.data["errors"] = response.data[field.name]; } break; + case 405: + showSnackIcon( + L10().response405, + success: false, + ); + break; // TODO: Other status codes? } @@ -895,7 +917,7 @@ class _APIFormWidgetState extends State { title: Text(title), actions: [ IconButton( - icon: FaIcon(FontAwesomeIcons.save), + icon: FaIcon(icon), onPressed: () { if (_formKey.currentState!.validate()) { diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 4eb234ec..03e7fc13 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -118,7 +118,7 @@ class InvenTreeModel { Map jsondata = {}; // Accessor for the API - InvenTreeAPI api = InvenTreeAPI(); + InvenTreeAPI get api => InvenTreeAPI(); int get pk => (jsondata["pk"] ?? -1) as int; @@ -167,11 +167,19 @@ class InvenTreeModel { String get url => "${URL}/${pk}/".replaceAll("//", "/"); // Search this Model type in the database - Future> search(BuildContext context, String searchTerm, {Map filters = const {}}) async { + Future> search(String searchTerm, {Map filters = const {}, int offset = 0, int limit = 25}) async { - filters["search"] = searchTerm; + Map searchFilters = {}; - final results = list(filters: filters); + for (String key in filters.keys) { + searchFilters[key] = filters[key] ?? ""; + } + + searchFilters["search"] = searchTerm; + searchFilters["offset"] = "${offset}"; + searchFilters["limit"] = "${limit}"; + + final results = list(filters: searchFilters); return results; diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 349066e3..ba979a28 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -20,6 +20,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel { @override String get URL => "order/po/"; + String get receive_url => "${url}receive/"; + @override Map formFields() { return { diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 4f2cbd23..c209310c 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -71,7 +71,7 @@ class PartSearchDelegate extends SearchDelegate { _filters["cascade"] = "true"; - final results = await InvenTreePart().search(context, query, filters: _filters); + final results = await InvenTreePart().search(query, filters: _filters); partResults.clear(); @@ -260,8 +260,7 @@ class StockSearchDelegate extends SearchDelegate { // Enable cascading part search by default _filters["cascade"] = "true"; - final results = await InvenTreeStockItem().search( - context, query, filters: _filters); + final results = await InvenTreeStockItem().search(query, filters: _filters); itemResults.clear(); diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 7ed69124..c10ac170 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -327,13 +327,7 @@ class _StockItemDisplayState extends RefreshableState { }, onFind: (String filter) async { - Map _filters = { - "search": filter, - "offset": "0", - "limit": "25" - }; - - final List results = await InvenTreeStockLocation().list(filters: _filters); + final results = await InvenTreeStockLocation().search(filter); List items = []; From 529510bc5c56dfcd56d7de5a8ac0511967af19ef Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 2 Oct 2021 22:33:59 +1000 Subject: [PATCH 45/70] Display HTTP error codes on form errors --- lib/api_form.dart | 37 +++++++++++++++++++++++++++++++++---- lib/l10n | 2 +- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 50e44d54..35d82dbe 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -885,7 +885,7 @@ class _APIFormWidgetState extends State { case 400: // Form submission / validation error showSnackIcon( - L10().error, + L10().formError, success: false ); @@ -894,13 +894,42 @@ class _APIFormWidgetState extends State { field.data["errors"] = response.data[field.name]; } break; - case 405: + case 401: showSnackIcon( - L10().response405, + "401: " + L10().response401, + success: false + ); + break; + case 403: + showSnackIcon( + "403: " + L10().response403, + success: false, + ); + break; + case 404: + showSnackIcon( + "404: " + L10().response404, + success: false, + ); + break; + case 405: + showSnackIcon( + "405: " + L10().response405, + success: false, + ); + break; + case 500: + showSnackIcon( + "500: " + L10().response500, + success: false, + ); + break; + default: + showSnackIcon( + "${response.statusCode}: " + L10().responseInvalid, success: false, ); break; - // TODO: Other status codes? } setState(() { diff --git a/lib/l10n b/lib/l10n index bdaf7a51..ef5ca26e 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit bdaf7a516cc40caf6ee1110fea6af073a61d7829 +Subproject commit ef5ca26effc8b203a0f49c0fd50afdc8ef2cc3aa From ce31d968b301796c411d9dd0f3b4846d440fb6f9 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 2 Oct 2021 23:34:37 +1000 Subject: [PATCH 46/70] Refactoring for APIFormField class - Class now stores field definitions as returned from the server - Complex lookup for multi-level form structures - Improved / increased error handling --- lib/api_form.dart | 252 ++++++++++++++++++++++++++++++++++------------ 1 file changed, 185 insertions(+), 67 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 35d82dbe..bb3b147a 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -38,53 +38,125 @@ class APIFormField { // JSON data which defines the field final Map data; + // JSON field definition provided by the server + Map definition = {}; + dynamic initial_data; + // Return the "lookup path" for this field, within the server data + String get lookupPath { + + // Simple top-level case + if (parent.isEmpty && !nested) { + return name; + } + + List path = []; + + if (parent.isNotEmpty) { + path.add(parent); + path.add("child"); + } + + if (nested) { + path.add("children"); + path.add(name); + } + + return path.join("."); + } + + /* + * Extract a field parameter from the provided field definition. + * + * - First the user-provided data is checked + * - Second, the server-provided definition is checked + * - Third, return null + */ + dynamic getParameter(String key) { + + if (data.containsKey(key)) { + return data[key]; + } else if (definition.containsKey(key)) { + return definition[key]; + } else { + return null; + } + } + // Get the "api_url" associated with a related field - String get api_url => (data["api_url"] ?? "") as String; + String get api_url => (getParameter("api_url") ?? "") as String; // Get the "model" associated with a related field - String get model => (data["model"] ?? "") as String; + String get model => (getParameter("model") ?? "") as String; // Is this field hidden? - bool get hidden => (data["hidden"] ?? false) as bool; + bool get hidden => (getParameter("hidden") ?? false) as bool; + + // Is this field nested? (Nested means part of an array) + // Note: This parameter is only defined locally + bool get nested => (data["nested"] ?? false) as bool; + + // What is the "parent" field of this field? + // Note: This parameter is only defined locally + String get parent => (data["parent"] ?? "") as String; // Is this field read only? - bool get readOnly => (data["read_only"] ?? false) as bool; + bool get readOnly => (getParameter("read_only") ?? false) as bool; - bool get multiline => (data["multiline"] ?? false) as bool; + bool get multiline => (getParameter("multiline") ?? false) as bool; // Get the "value" as a string (look for "default" if not available) - dynamic get value => data["value"] ?? data["default"]; + dynamic get value => getParameter("value") ?? data["default"]; // Get the "default" as a string - dynamic get defaultValue => data["default"]; + dynamic get defaultValue => getParameter("default"); + // Construct a set of "filters" for this field (e.g. related field) Map get filters { Map _filters = {}; - // Start with the provided "model" filters - if (data.containsKey("filters")) { + // Start with the field "definition" (provided by the server) + if (definition.containsKey("filters")) { - dynamic f = data["filters"]; + try { + var fDef = definition["filters"] as Map; - if (f is Map) { - f.forEach((key, value) { - _filters[key as String] = value.toString(); + fDef.forEach((String key, dynamic value) { + _filters[key] = value.toString(); }); + + } catch (error) { + // pass } } - // Now, look at the provided "instance_filters" - if (data.containsKey("instance_filters")) { + // Next, look at any "instance_filters" provided by the server + if (definition.containsKey("instance_filters")) { - dynamic f = data["instance_filters"]; + try { + var fIns = definition["instance_filters"] as Map; - if (f is Map) { - f.forEach((key, value) { - _filters[key as String] = value.toString(); + fIns.forEach((String key, dynamic value) { + _filters[key] = value.toString(); }); + } catch (error) { + // pass + } + + } + + // Finally, augment or override with any filters provided by the calling function + if (data.containsKey("filters")) { + try { + var fDat = data["filters"] as Map; + + fDat.forEach((String key, dynamic value) { + _filters[key] = value.toString(); + }); + } catch (error) { + // pass } } @@ -108,17 +180,17 @@ class APIFormField { } // Is this field required? - bool get required => (data["required"] ?? false) as bool; + bool get required => (getParameter("required") ?? false) as bool; - String get type => (data["type"] ?? "").toString(); + String get type => (getParameter("type") ?? "").toString(); - String get label => (data["label"] ?? "").toString(); + String get label => (getParameter("label") ?? "").toString(); - String get helpText => (data["help_text"] ?? "").toString(); + String get helpText => (getParameter("help_text") ?? "").toString(); - String get placeholderText => (data["placeholder"] ?? "").toString(); + String get placeholderText => (getParameter("placeholder") ?? "").toString(); - List get choices => (data["choices"] ?? []) as List; + List get choices => (getParameter("choices") ?? []) as List; Future loadInitialData() async { @@ -547,6 +619,72 @@ Map extractFields(APIResponse response) { return result as Map; } +/* + * Extract a field definition (map) from the provided JSON data. + * + * Notes: + * - If the field is a top-level item, the provided "path" may be a simple string (e.g. "quantity"), + * - If the field is buried in the JSON data, the "path" may use a dotted notation e.g. "items.child.children.quantity" + * + * The map "tree" is traversed based on the provided lookup string, which can use dotted notation. + * This allows complex paths to be used to lookup field information. + */ +Map extractFieldDefinition(Map data, String lookup) { + + List path = lookup.split("."); + + // Shadow copy the data for path traversal + Map _data = data; + + // Iterate through all but the last element of the path + for (int ii = 0; ii < (path.length - 1); ii++) { + + String el = path[ii]; + + if (!_data.containsKey(el)) { + print("Could not find field definition for ${lookup}:"); + print("- Key ${el} missing at index ${ii}"); + return {}; + } + + try { + _data = _data[el] as Map; + } catch (error, stackTrace) { + print("Could not find sub-field element '${el}' for ${lookup}:"); + print(error.toString()); + + // Report the error + sentryReportError(error, stackTrace); + return {}; + } + } + + String el = path.last; + + if (!_data.containsKey(el)) { + print("Could not find field definition for ${lookup}"); + print("- Final field path ${el} missing from data"); + return {}; + } else { + + try { + Map definition = _data[el] as Map; + + return definition; + } catch (error, stacktrace) { + print("Could not find field definition for ${lookup}"); + print(error.toString()); + + // Report the error + sentryReportError(error, stacktrace); + + return {}; + } + + } +} + + /* * Launch an API-driven form, * which uses the OPTIONS metadata (at the provided URL) @@ -577,9 +715,10 @@ Future launchApiForm( return; } - var availableFields = extractFields(options); + // List of fields defined by the server + Map serverFields = extractFields(options); - if (availableFields.isEmpty) { + if (serverFields.isEmpty) { // User does not have permission to perform this action showSnackIcon( L10().response403, @@ -592,53 +731,32 @@ Future launchApiForm( // Construct a list of APIFormField objects List formFields = []; - // Iterate through the provided fields we wish to display + APIFormField field; + for (String fieldName in fields.keys) { - // Check that the field is actually available at the API endpoint - if (!availableFields.containsKey(fieldName)) { - print("Field '${fieldName}' not available at '${url}'"); + dynamic data = fields[fieldName]; - sentryReportMessage( - "API form called with unknown field '${fieldName}'", - context: { - "url": url.toString(), - } - ); + Map fieldData = {}; + if (data is Map) { + fieldData = Map.from(data); + } + + // Iterate through the provided fields we wish to display + + field = APIFormField(fieldName, fieldData); + + // Extract the definition of this field from the data received from the server + field.definition = extractFieldDefinition(serverFields, field.lookupPath); + + // Skip fields with empty definitions + if (field.definition.isEmpty) { + print("ERROR: Empty field definition for field '${fieldName}'"); continue; } - final remoteField = Map.from(availableFields[fieldName] as Map); - final localField = Map.from(fields[fieldName] as Map); - - // Override defined field parameters, if provided - for (String key in localField.keys) { - // Special consideration must be taken here! - if (key == "filters") { - - if (!remoteField.containsKey("filters")) { - remoteField["filters"] = {}; - } - - var filters = localField["filters"]; - - if (filters is Map) { - filters.forEach((key, value) { - remoteField["filters"][key] = value; - }); - } - - } else { - remoteField[key] = localField[key]; - } - } - - if (modelData.containsKey(fieldName)) { - remoteField["value"] = modelData[fieldName]; - } - - formFields.add(APIFormField(fieldName, remoteField)); + formFields.add(field); } // Grab existing data for each form field From 80b203ce7beb21c59f7c76456fd3b2a5834eb43b Mon Sep 17 00:00:00 2001 From: Oliver Date: Sat, 2 Oct 2021 23:47:33 +1000 Subject: [PATCH 47/70] Reimplement instance data --- lib/api_form.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index bb3b147a..f6e7fcf0 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -71,10 +71,10 @@ class APIFormField { * * - First the user-provided data is checked * - Second, the server-provided definition is checked - * - Third, return null + * + * - Finally, return null */ dynamic getParameter(String key) { - if (data.containsKey(key)) { return data[key]; } else if (definition.containsKey(key)) { @@ -107,7 +107,7 @@ class APIFormField { bool get multiline => (getParameter("multiline") ?? false) as bool; // Get the "value" as a string (look for "default" if not available) - dynamic get value => getParameter("value") ?? data["default"]; + dynamic get value => data["value"] ?? data["instance_value"] ?? data["default"]; // Get the "default" as a string dynamic get defaultValue => getParameter("default"); @@ -756,6 +756,9 @@ Future launchApiForm( continue; } + // Add instance value to the field + field.data["instance_value"] = modelData[fieldName]; + formFields.add(field); } From b7f9f1c55f85640d1160e72754280fb9fe9d0ba0 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 00:40:26 +1000 Subject: [PATCH 48/70] Handle form posting with complex "layered" data - Handle data rendering - Handle returned errors --- lib/api.dart | 17 +++--- lib/api_form.dart | 86 +++++++++++++++++++++++++++---- lib/inventree/purchase_order.dart | 2 + 3 files changed, 88 insertions(+), 17 deletions(-) diff --git a/lib/api.dart b/lib/api.dart index 64993608..64a74682 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -485,11 +485,9 @@ class InvenTreeAPI { // Perform a PATCH request - Future patch(String url, {Map body = const {}, int? expectedStatusCode}) async { - Map _body = {}; + Future patch(String url, {Map body = const {}, int? expectedStatusCode}) async { - // Copy across provided data - body.forEach((K, V) => _body[K] = V); + Map _body = body; HttpClientRequest? request = await apiRequest(url, "PATCH"); @@ -599,7 +597,7 @@ class InvenTreeAPI { * Upload a file to the given URL */ Future uploadFile(String url, File f, - {String name = "attachment", String method="POST", Map? fields}) async { + {String name = "attachment", String method="POST", Map? fields}) async { var _url = makeApiUrl(url); var request = http.MultipartRequest(method, Uri.parse(_url)); @@ -607,8 +605,13 @@ class InvenTreeAPI { request.headers.addAll(defaultHeaders()); if (fields != null) { - fields.forEach((String key, String value) { - request.fields[key] = value; + fields.forEach((String key, dynamic value) { + + if (value == null) { + request.fields[key] = ""; + } else { + request.fields[key] = value.toString(); + } }); } diff --git a/lib/api_form.dart b/lib/api_form.dart index f6e7fcf0..4204213b 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -101,13 +101,24 @@ class APIFormField { // Note: This parameter is only defined locally String get parent => (data["parent"] ?? "") as String; + bool get isSimple => !nested && parent.isEmpty; + // Is this field read only? bool get readOnly => (getParameter("read_only") ?? false) as bool; bool get multiline => (getParameter("multiline") ?? false) as bool; // Get the "value" as a string (look for "default" if not available) - dynamic get value => data["value"] ?? data["instance_value"] ?? data["default"]; + dynamic get value => data["value"] ?? data["instance_value"] ?? defaultValue; + + // Render value to string (for form submission) + String renderToString() { + if (value == null) { + return ""; + } else { + return value.toString(); + } + } // Get the "default" as a string dynamic get defaultValue => getParameter("default"); @@ -166,6 +177,28 @@ class APIFormField { bool hasErrors() => errorMessages().isNotEmpty; + // Extract error messages from the server response + void extractErrorMessages(APIResponse response) { + + if (isSimple) { + // Simple fields are easily handled + data["errors"] = response.data[name]; + } else { + if (parent.isNotEmpty) { + dynamic parentElement = response.data[parent]; + + // Extract from list + if (parentElement is List) { + parentElement = parentElement[0]; + } + + if (parentElement is Map) { + data["errors"] = parentElement[name]; + } + } + } + } + // Return the error message associated with this field List errorMessages() { List errors = (data["errors"] ?? []) as List; @@ -902,8 +935,7 @@ class _APIFormWidgetState extends State { return widgets; } - Future _submit(Map data) async { - + Future _submit(Map data) async { // If a file upload is required, we have to handle the submission differently if (fileField.isNotEmpty) { @@ -953,22 +985,50 @@ class _APIFormWidgetState extends State { } + /* + * Submit the form data to the server, and handle the results + */ Future _save(BuildContext context) async { // Package up the form data - Map data = {}; + Map data = {}; + + // Iterate through and find "simple" top-level fields for (var field in fields) { - dynamic value = field.value; - - if (value == null) { - data[field.name] = ""; + if (field.isSimple) { + // Simple top-level field data + data[field.name] = field.renderToString(); } else { - data[field.name] = value.toString(); + // Not so simple... (WHY DID I MAKE THE API SO COMPLEX?) + if (field.parent.isNotEmpty) { + + // TODO: This is a dirty hack, there *must* be a cleaner way?! + + dynamic parent = data[field.parent] ?? {}; + + // In the case of a "nested" object, we need to extract the first item + if (parent is List) { + parent = parent.first; + } + + parent[field.name] = field.renderToString(); + + // Nested fields must be handled as an array! + // For now, we only allow single length nested fields + if (field.nested) { + parent = [parent]; + } + + data[field.parent] = parent; + } } } + print("Submitting form data to server:"); + print(data.toString()); + final response = await _submit(data); if (!response.isValid()) { @@ -976,6 +1036,9 @@ class _APIFormWidgetState extends State { return; } + print("Response: ${response.statusCode}"); + print(response.data.toString()); + switch (response.statusCode) { case 200: case 201: @@ -1010,10 +1073,13 @@ class _APIFormWidgetState extends State { success: false ); + print(response.data); + // Update field errors for (var field in fields) { - field.data["errors"] = response.data[field.name]; + field.extractErrorMessages(response); } + break; case 401: showSnackIcon( diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index ba979a28..6b8595cf 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -85,6 +85,8 @@ class InvenTreePurchaseOrder extends InvenTreeModel { bool get isOpen => status == PO_STATUS_PENDING || status == PO_STATUS_PLACED; + bool get isPlaced => status == PO_STATUS_PLACED; + bool get isFailed => status == PO_STATUS_CANCELLED || status == PO_STATUS_LOST || status == PO_STATUS_RETURNED; Future> getLineItems() async { From 2886f7c9309ed122f1898b5dd32b5118e4169f51 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 01:20:37 +1000 Subject: [PATCH 49/70] Display non-field errors as returned from the server --- lib/api_form.dart | 71 ++++++++++++++++++++++++++++++++++-- lib/helpers.dart | 13 +++++++ lib/inventree/stock.dart | 21 +++-------- lib/widget/stock_detail.dart | 2 +- 4 files changed, 87 insertions(+), 20 deletions(-) create mode 100644 lib/helpers.dart diff --git a/lib/api_form.dart b/lib/api_form.dart index 4204213b..c83b945a 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -7,6 +7,7 @@ import "package:date_field/date_field.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; +import 'package:inventree/helpers.dart'; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; @@ -201,7 +202,15 @@ class APIFormField { // Return the error message associated with this field List errorMessages() { - List errors = (data["errors"] ?? []) as List; + + dynamic errors = data["errors"] ?? []; + + // Handle the case where a single error message is returned + if (errors is String) { + errors = [errors]; + } + + errors = errors as List; List messages = []; @@ -395,7 +404,7 @@ class APIFormField { helperStyle: _helperStyle(), hintText: placeholderText, ), - initialValue: (value ?? 0).toString(), + initialValue: simpleNumberString(double.tryParse(value.toString()) ?? 0), keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true), validator: (value) { @@ -871,6 +880,8 @@ class _APIFormWidgetState extends State { final IconData icon; + List nonFieldErrors = []; + List fields; Function(Map)? onSuccess; @@ -881,6 +892,29 @@ class _APIFormWidgetState extends State { List widgets = []; + // Display non-field errors first + if (nonFieldErrors.isNotEmpty) { + for (String error in nonFieldErrors) { + widgets.add( + ListTile( + title: Text( + error, + style: TextStyle( + color: COLOR_DANGER, + ), + ), + leading: FaIcon( + FontAwesomeIcons.exclamationCircle, + color: COLOR_DANGER + ), + ) + ); + } + + widgets.add(Divider(height: 5)); + + } + for (var field in fields) { if (field.hidden) { @@ -982,7 +1016,36 @@ class _APIFormWidgetState extends State { return response; } + } + void extractNonFieldErrors(APIResponse response) { + + List errors = []; + + Map data = response.asMap(); + + // Potential keys representing non-field errors + List keys = [ + "__all__", + "non_field_errors", + "errors", + ]; + + for (String key in keys) { + if (data.containsKey(key)) { + dynamic result = data[key]; + + if (result is String) { + errors.add(result); + } else if (result is List) { + result.forEach((element) { + errors.add(element.toString()); + }); + } + } + } + + nonFieldErrors = errors; } /* @@ -1073,13 +1136,13 @@ class _APIFormWidgetState extends State { success: false ); - print(response.data); - // Update field errors for (var field in fields) { field.extractErrorMessages(response); } + extractNonFieldErrors(response); + break; case 401: showSnackIcon( diff --git a/lib/helpers.dart b/lib/helpers.dart new file mode 100644 index 00000000..942c98ec --- /dev/null +++ b/lib/helpers.dart @@ -0,0 +1,13 @@ +/* + * A set of helper functions to reduce boilerplate code + */ + +/* + * Simplify a numerical value into a string, + * supressing trailing zeroes + */ +String simpleNumberString(double number) { + // Ref: https://stackoverflow.com/questions/55152175/how-to-remove-trailing-zeros-using-dart + + return number.toStringAsFixed(number.truncateToDouble() == number ? 0 : 1); +} \ No newline at end of file diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 82c37711..1aa709a7 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,6 +1,7 @@ import "dart:async"; import "package:intl/intl.dart"; +import 'package:inventree/helpers.dart'; import "package:inventree/inventree/part.dart"; import "package:flutter/cupertino.dart"; @@ -369,16 +370,11 @@ class InvenTreeStockItem extends InvenTreeModel { double get quantity => double.tryParse(jsondata["quantity"].toString()) ?? 0; - String get quantityString { + String quantityString({bool includeUnits = false}){ - String q = quantity.toString(); + String q = simpleNumberString(quantity); - // Simplify integer values e.g. "1.0" becomes "1" - if (quantity.toInt() == quantity) { - q = quantity.toInt().toString(); - } - - if (units.isNotEmpty) { + if (includeUnits && units.isNotEmpty) { q += " ${units}"; } @@ -394,12 +390,7 @@ class InvenTreeStockItem extends InvenTreeModel { return "SN ${serialNumber}"; } - // Is an integer? - if (quantity.toInt() == quantity) { - return "${quantity.toInt()}"; - } - - return "${quantity}"; + return simpleNumberString(quantity); } String get locationName { @@ -436,7 +427,7 @@ class InvenTreeStockItem extends InvenTreeModel { if (serialNumber.isNotEmpty) { return "SN: $serialNumber"; } else { - return quantityString; + return simpleNumberString(quantity); } } diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index c10ac170..2fa41ddb 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -622,7 +622,7 @@ class _StockItemDisplayState extends RefreshableState { title: Text(L10().countStock), leading: FaIcon(FontAwesomeIcons.checkCircle, color: COLOR_CLICK), onTap: _countStockDialog, - trailing: Text(item.quantityString), + trailing: Text(item.quantityString(includeUnits: true)), ) ); From e9fa0f5eb4f31546eee1e75f45c1c2936a58eb6d Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 01:21:27 +1000 Subject: [PATCH 50/70] Remove some debug messages --- lib/api_form.dart | 8 +------- lib/l10n | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index c83b945a..3a917fb8 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -343,7 +343,7 @@ class APIFormField { FilePickerDialog.pickFile( message: L10().attachmentSelect, onPicked: (file) { - print("${file.path}"); + // print("${file.path}"); // Display the filename controller.text = file.path.split("/").last; @@ -1089,9 +1089,6 @@ class _APIFormWidgetState extends State { } } - print("Submitting form data to server:"); - print(data.toString()); - final response = await _submit(data); if (!response.isValid()) { @@ -1099,9 +1096,6 @@ class _APIFormWidgetState extends State { return; } - print("Response: ${response.statusCode}"); - print(response.data.toString()); - switch (response.statusCode) { case 200: case 201: diff --git a/lib/l10n b/lib/l10n index ef5ca26e..603e0420 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit ef5ca26effc8b203a0f49c0fd50afdc8ef2cc3aa +Subproject commit 603e0420a44996270e7cc29b278d8bdbf931e5c4 From 6ff99b15c3839566f525b8cbc64ccc810d3c67eb Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 01:25:45 +1000 Subject: [PATCH 51/70] Clear errors once fixed --- lib/api_form.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 3a917fb8..fc4b3a36 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -181,9 +181,11 @@ class APIFormField { // Extract error messages from the server response void extractErrorMessages(APIResponse response) { + dynamic errors = null; + if (isSimple) { // Simple fields are easily handled - data["errors"] = response.data[name]; + errors = response.data[name]; } else { if (parent.isNotEmpty) { dynamic parentElement = response.data[parent]; @@ -194,10 +196,12 @@ class APIFormField { } if (parentElement is Map) { - data["errors"] = parentElement[name]; + errors = parentElement[name]; } } } + + data["errors"] = errors; } // Return the error message associated with this field From 65180c7b2f6116b6fc2b938b415c31422b6a73e8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 01:33:16 +1000 Subject: [PATCH 52/70] Receive stock against purchase order line items --- lib/inventree/stock.dart | 3 +- lib/l10n | 2 +- lib/widget/location_display.dart | 5 +- lib/widget/purchase_order_detail.dart | 97 +++++++++++++++++++++++++-- lib/widget/stock_detail.dart | 2 +- 5 files changed, 100 insertions(+), 9 deletions(-) diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 1aa709a7..22b5e709 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,5 +1,6 @@ import "dart:async"; +import 'package:flutter/material.dart'; import "package:intl/intl.dart"; import 'package:inventree/helpers.dart'; import "package:inventree/inventree/part.dart"; @@ -98,7 +99,7 @@ class InvenTreeStockItem extends InvenTreeModel { Color get statusColor { switch (status) { case OK: - return Color(0xFF50aa51); + return Colors.black; case ATTENTION: return Color(0xFFfdc82a); case DAMAGED: diff --git a/lib/l10n b/lib/l10n index 603e0420..74d796f7 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit 603e0420a44996270e7cc29b278d8bdbf931e5c4 +Subproject commit 74d796f7f1174fc27d5c031e18f4adbe1e16e6a4 diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 145e9982..26950a34 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -580,7 +580,10 @@ class _PaginatedStockListState extends State { height: 40, ), trailing: Text("${item.displayQuantity}", - style: TextStyle(fontWeight: FontWeight.bold), + style: TextStyle( + fontWeight: FontWeight.bold, + color: item.statusColor, + ), ), onTap: () { _openItem(context, item.pk); diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index b89117d9..641e5402 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -1,15 +1,25 @@ +import 'package:dropdown_search/dropdown_search.dart'; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; +import 'package:inventree/api_form.dart'; import "package:inventree/app_colors.dart"; +import 'package:inventree/helpers.dart'; import "package:inventree/inventree/company.dart"; +import 'package:inventree/inventree/model.dart'; +import 'package:inventree/inventree/part.dart'; import "package:inventree/inventree/purchase_order.dart"; +import 'package:inventree/inventree/stock.dart'; import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; import "package:inventree/widget/location_display.dart"; +import 'package:inventree/widget/snacks.dart'; +import 'package:one_context/one_context.dart'; + +import 'dialogs.dart'; class PurchaseOrderDetailWidget extends StatefulWidget { @@ -172,15 +182,70 @@ class _PurchaseOrderDetailState extends RefreshableState fields = { + "line_item": { + "parent": "items", + "nested": true, + "hidden": true, + "value": lineItem.pk, + }, + "quantity": { + "parent": "items", + "nested": true, + "value": lineItem.outstanding, + }, + "status": { + "parent": "items", + "nested": true, + }, + "location": { + } + }; + + // TODO: Pre-fill the "location" value if the part has a default location specified + + launchApiForm( + context, + L10().receiveItem, + order.receive_url, + fields, + method: "POST", + icon: FontAwesomeIcons.signInAlt, + onSuccess: (data) async { + showSnackIcon(L10().receivedItem, success: true); + refresh(); + } + ); + } + void lineItemMenu(BuildContext context, InvenTreePOLineItem lineItem) { List children = []; - if (InvenTreeAPI().supportPoReceive()) { + children.add( + SimpleDialogOption( + onPressed: () { + OneContext().popDialog(); + + // TODO: Navigate to the "SupplierPart" display? + }, + child: ListTile( + title: Text(L10().viewSupplierPart), + leading: FaIcon(FontAwesomeIcons.eye), + ) + ) + ); + + if (order.isPlaced && InvenTreeAPI().supportPoReceive()) { children.add( SimpleDialogOption( onPressed: () { + // Hide the dialog option + OneContext().popDialog(); + receiveLine(context, lineItem); }, child: ListTile( title: Text(L10().receiveItem), @@ -219,15 +284,37 @@ class _PurchaseOrderDetailState extends RefreshableState { ListTile( title: Text(L10().quantity), leading: FaIcon(FontAwesomeIcons.cubes), - trailing: Text("${item.quantityString}"), + trailing: Text("${item.quantityString()}"), ) ); } From 9eaf4a09da0993d918a47e9df064c1058f9239fa Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 01:36:46 +1000 Subject: [PATCH 53/70] Linting fixes --- lib/api_form.dart | 8 ++++---- lib/inventree/stock.dart | 4 ++-- lib/widget/purchase_order_detail.dart | 14 ++++---------- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index fc4b3a36..7fedbcdc 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -7,7 +7,7 @@ import "package:date_field/date_field.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; -import 'package:inventree/helpers.dart'; +import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; @@ -181,7 +181,7 @@ class APIFormField { // Extract error messages from the server response void extractErrorMessages(APIResponse response) { - dynamic errors = null; + dynamic errors; if (isSimple) { // Simple fields are easily handled @@ -1042,9 +1042,9 @@ class _APIFormWidgetState extends State { if (result is String) { errors.add(result); } else if (result is List) { - result.forEach((element) { + for (dynamic element in result) { errors.add(element.toString()); - }); + } } } } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 22b5e709..a0aec8b4 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -1,8 +1,8 @@ import "dart:async"; -import 'package:flutter/material.dart'; +import "package:flutter/material.dart"; import "package:intl/intl.dart"; -import 'package:inventree/helpers.dart'; +import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.dart"; import "package:flutter/cupertino.dart"; diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 641e5402..6d6881cf 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -1,25 +1,19 @@ -import 'package:dropdown_search/dropdown_search.dart'; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/api.dart"; -import 'package:inventree/api_form.dart'; +import "package:inventree/api_form.dart"; import "package:inventree/app_colors.dart"; -import 'package:inventree/helpers.dart'; +import "package:inventree/helpers.dart"; import "package:inventree/inventree/company.dart"; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/inventree/part.dart'; import "package:inventree/inventree/purchase_order.dart"; -import 'package:inventree/inventree/stock.dart'; import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; import "package:inventree/widget/location_display.dart"; -import 'package:inventree/widget/snacks.dart'; -import 'package:one_context/one_context.dart'; - -import 'dialogs.dart'; +import "package:inventree/widget/snacks.dart"; +import "package:one_context/one_context.dart"; class PurchaseOrderDetailWidget extends StatefulWidget { From 38e4735f777d4311cdc270bc8fb02ecb240e538c Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 11:06:27 +1100 Subject: [PATCH 54/70] Add a custom "back" button to refreshable state - Long press to return to the "home" screen --- lib/inventree/stock.dart | 4 ++++ lib/widget/back.dart | 27 +++++++++++++++++++++++++++ lib/widget/refreshable_state.dart | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 lib/widget/back.dart diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index a0aec8b4..45606a2d 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -473,6 +473,7 @@ class InvenTreeStockItem extends InvenTreeModel { return response.isValid(); } + // TODO: Refactor this once the server supports API metadata for this action Future countStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock(context, "/stock/count/", q, notes: notes); @@ -480,6 +481,7 @@ class InvenTreeStockItem extends InvenTreeModel { return result; } + // TODO: Refactor this once the server supports API metadata for this action Future addStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock(context, "/stock/add/", q, notes: notes); @@ -487,6 +489,7 @@ class InvenTreeStockItem extends InvenTreeModel { return result; } + // TODO: Refactor this once the server supports API metadata for this action Future removeStock(BuildContext context, double q, {String? notes}) async { final bool result = await adjustStock(context, "/stock/remove/", q, notes: notes); @@ -494,6 +497,7 @@ class InvenTreeStockItem extends InvenTreeModel { return result; } + // TODO: Refactor this once the server supports API metadata for this action Future transferStock(int location, {double? quantity, String? notes}) async { if ((quantity == null) || (quantity < 0) || (quantity > this.quantity)) { quantity = this.quantity; diff --git a/lib/widget/back.dart b/lib/widget/back.dart new file mode 100644 index 00000000..587a69b3 --- /dev/null +++ b/lib/widget/back.dart @@ -0,0 +1,27 @@ +/* + * A custom implementation of a "Back" button for display in the app drawer + * + * Long-pressing on this will return the user to the home screen + */ + +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; + +Widget backButton(BuildContext context) { + + return GestureDetector( + onLongPress: () { + while (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }, + child: IconButton( + icon: BackButtonIcon(), + onPressed: () { + if (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } + }, + ), + ); +} \ No newline at end of file diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index fb1e3559..0b8cd2b8 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -1,3 +1,4 @@ +import 'package:inventree/widget/back.dart'; import "package:inventree/widget/drawer.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; @@ -66,6 +67,7 @@ abstract class RefreshableState extends State { return AppBar( title: Text(getAppBarTitle(context)), actions: getAppBarActions(context), + leading: backButton(context), ); } From 6288088a655ebda5080c3ab3c2a526b21bc14846 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 11:36:41 +1100 Subject: [PATCH 55/70] Adds "barcode" field handler for API forms --- lib/api_form.dart | 53 ++++++++++++++++++++++- lib/barcode.dart | 61 ++++++++++++++++++++++++++- lib/l10n | 2 +- lib/widget/purchase_order_detail.dart | 7 +++ 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 7fedbcdc..74c62ff6 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -7,6 +7,7 @@ import "package:date_field/date_field.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; +import 'package:inventree/barcode.dart'; import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; @@ -269,7 +270,7 @@ class APIFormField { } // Construct a widget for this input - Widget constructField() { + Widget constructField(BuildContext context) { switch (type) { case "string": case "url": @@ -288,6 +289,8 @@ class APIFormField { return _constructFileField(); case "date": return _constructDateField(); + case "barcode": + return _constructBarcodeField(context); default: return ListTile( title: Text( @@ -300,6 +303,52 @@ class APIFormField { } } + // Field for capturing a barcode + Widget _constructBarcodeField(BuildContext context) { + + TextEditingController controller = TextEditingController(); + + String barcode = (value ?? "").toString(); + + if (barcode.isEmpty) { + barcode = L10().barcodeNotAssigned; + } + + controller.text = barcode; + + return InputDecorator( + decoration: InputDecoration( + labelText: required ? label + "*" : label, + labelStyle: _labelStyle(), + helperText: helpText, + helperStyle: _helperStyle(), + hintText: placeholderText, + ), + child: ListTile( + title: TextField( + readOnly: true, + controller: controller, + ), + trailing: IconButton( + icon: FaIcon(FontAwesomeIcons.qrcode), + onPressed: () async { + + var handler = UniqueBarcodeHandler((String hash) { + print("Scanned barcode: " + hash); + }); + + Navigator.push( + context, + MaterialPageRoute(builder: (context) => InvenTreeQRView(handler) + ) + ); + }, + ), + ) + ); + + } + // Field for displaying and selecting dates Widget _constructDateField() { @@ -937,7 +986,7 @@ class _APIFormWidgetState extends State { } } - widgets.add(field.constructField()); + widgets.add(field.constructField(context)); if (field.hasErrors()) { for (String error in field.errorMessages()) { diff --git a/lib/barcode.dart b/lib/barcode.dart index 3f44aa9c..de6283f0 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -305,7 +305,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { } ).then((result) { if (result) { - failureTone(); + successTone(); Navigator.of(context).pop(); @@ -315,7 +315,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler { icon: FontAwesomeIcons.qrcode ); } else { - successTone(); + failureTone(); showSnackIcon( L10().barcodeNotAssigned, @@ -464,6 +464,63 @@ class StockLocationScanInItemsHandler extends BarcodeHandler { } +class UniqueBarcodeHandler extends BarcodeHandler { + /* + * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) + */ + + UniqueBarcodeHandler(this.callback); + + // Callback function when a "unique" barcode hash is found + final Function(String) callback; + + @override + String getOverlayText(BuildContext context) => L10().barcodeScanAssign; + + @override + Future onBarcodeMatched(BuildContext context, Map data) async { + + failureTone(); + + // If the barcode is known, we can"t assign it to the stock item! + showSnackIcon( + L10().barcodeInUse, + icon: FontAwesomeIcons.qrcode, + success: false + ); + } + + @override + Future onBarcodeUnknown(BuildContext context, Map data) async { + // If the barcode is unknown, we *can* assign it to the stock item! + + if (!data.containsKey("hash")) { + showServerError( + L10().missingData, + L10().barcodeMissingHash, + ); + } else { + String hash = (data["hash"] ?? "") as String; + + if (hash.isEmpty) { + failureTone(); + + showSnackIcon( + L10().barcodeError, + success: false, + ); + } else { + + // Close the barcode scanner + Navigator.of(context).pop(); + + callback(hash); + } + } + } +} + + class InvenTreeQRView extends StatefulWidget { const InvenTreeQRView(this._handler, {Key? key}) : super(key: key); diff --git a/lib/l10n b/lib/l10n index 74d796f7..5f30fef9 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit 74d796f7f1174fc27d5c031e18f4adbe1e16e6a4 +Subproject commit 5f30fef900ea027cd7fe90b2d87a6f02ced315c0 diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 6d6881cf..9a5cd703 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -195,6 +195,13 @@ class _PurchaseOrderDetailState extends RefreshableState Date: Sun, 3 Oct 2021 11:41:16 +1100 Subject: [PATCH 56/70] Update form field with scanned barcode --- lib/api_form.dart | 4 ++-- lib/barcode.dart | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 74c62ff6..1a7237f6 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -332,9 +332,9 @@ class APIFormField { trailing: IconButton( icon: FaIcon(FontAwesomeIcons.qrcode), onPressed: () async { - var handler = UniqueBarcodeHandler((String hash) { - print("Scanned barcode: " + hash); + controller.text = hash; + data["value"] = hash; }); Navigator.push( diff --git a/lib/barcode.dart b/lib/barcode.dart index de6283f0..76bb4fe2 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -511,9 +511,16 @@ class UniqueBarcodeHandler extends BarcodeHandler { ); } else { + successTone(); + // Close the barcode scanner Navigator.of(context).pop(); + showSnackIcon( + L10().barcodeAssigned, + success: true + ); + callback(hash); } } From 4f8794e6528c6d623892f9bc48037e7ff4720cbe Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 12:04:01 +1100 Subject: [PATCH 57/70] Refactor barcode scann function - Provide a custom callback to be executed when the barcode is scanned --- lib/api_form.dart | 7 +++ lib/barcode.dart | 110 ++++------------------------------- lib/helpers.dart | 24 ++++++++ lib/widget/stock_detail.dart | 31 ++++++++-- 4 files changed, 67 insertions(+), 105 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 1a7237f6..799f1864 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -335,6 +335,13 @@ class APIFormField { var handler = UniqueBarcodeHandler((String hash) { controller.text = hash; data["value"] = hash; + + successTone(); + + showSnackIcon( + L10().barcodeAssigned, + success: true + ); }); Navigator.push( diff --git a/lib/barcode.dart b/lib/barcode.dart index 76bb4fe2..aeeba495 100644 --- a/lib/barcode.dart +++ b/lib/barcode.dart @@ -1,10 +1,8 @@ import "dart:io"; -import "package:inventree/app_settings.dart"; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/snacks.dart"; -import "package:audioplayers/audioplayers.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; @@ -15,7 +13,7 @@ import "package:qr_code_scanner/qr_code_scanner.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/l10.dart"; - +import "package:inventree/helpers.dart"; import "package:inventree/api.dart"; import "package:inventree/widget/location_display.dart"; @@ -38,26 +36,6 @@ class BarcodeHandler { QRViewController? _controller; - Future successTone() async { - - final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; - - if (en) { - final player = AudioCache(); - player.play("sounds/barcode_scan.mp3"); - } - } - - Future failureTone() async { - - final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; - - if (en) { - final player = AudioCache(); - player.play("sounds/barcode_error.mp3"); - } - } - Future onBarcodeMatched(BuildContext context, Map data) async { // Called when the server "matches" a barcode // Override this function @@ -260,75 +238,6 @@ class BarcodeScanHandler extends BarcodeHandler { } } - -class StockItemBarcodeAssignmentHandler extends BarcodeHandler { - /* - * Barcode handler for assigning a new barcode to a stock item - */ - - StockItemBarcodeAssignmentHandler(this.item); - - final InvenTreeStockItem item; - - @override - String getOverlayText(BuildContext context) => L10().barcodeScanAssign; - - @override - Future onBarcodeMatched(BuildContext context, Map data) async { - - failureTone(); - - // If the barcode is known, we can"t assign it to the stock item! - showSnackIcon( - L10().barcodeInUse, - icon: FontAwesomeIcons.qrcode, - success: false - ); - } - - @override - Future onBarcodeUnknown(BuildContext context, Map data) async { - // If the barcode is unknown, we *can* assign it to the stock item! - - if (!data.containsKey("hash")) { - showServerError( - L10().missingData, - L10().barcodeMissingHash, - ); - } else { - String hash = (data["hash"] ?? "") as String; - - if (hash.isNotEmpty) { - item.update( - values: { - "uid": hash, - } - ).then((result) { - if (result) { - successTone(); - - Navigator.of(context).pop(); - - showSnackIcon( - L10().barcodeAssigned, - success: true, - icon: FontAwesomeIcons.qrcode - ); - } else { - failureTone(); - - showSnackIcon( - L10().barcodeNotAssigned, - success: false, - icon: FontAwesomeIcons.qrcode - ); - } - }); - } - } - } -} - class StockItemScanIntoLocationHandler extends BarcodeHandler { /* * Barcode handler for scanning a provided StockItem into a scanned StockLocation @@ -469,13 +378,21 @@ class UniqueBarcodeHandler extends BarcodeHandler { * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) */ - UniqueBarcodeHandler(this.callback); + UniqueBarcodeHandler(this.callback, {this.overlayText = ""}); // Callback function when a "unique" barcode hash is found final Function(String) callback; + final String overlayText; + @override - String getOverlayText(BuildContext context) => L10().barcodeScanAssign; + String getOverlayText(BuildContext context) { + if (overlayText.isEmpty) { + return L10().barcodeScanAssign; + } else { + return overlayText; + } + } @override Future onBarcodeMatched(BuildContext context, Map data) async { @@ -516,11 +433,6 @@ class UniqueBarcodeHandler extends BarcodeHandler { // Close the barcode scanner Navigator.of(context).pop(); - showSnackIcon( - L10().barcodeAssigned, - success: true - ); - callback(hash); } } diff --git a/lib/helpers.dart b/lib/helpers.dart index 942c98ec..8b3e1764 100644 --- a/lib/helpers.dart +++ b/lib/helpers.dart @@ -6,8 +6,32 @@ * Simplify a numerical value into a string, * supressing trailing zeroes */ + +import "package:audioplayers/audioplayers.dart"; +import "package:inventree/app_settings.dart"; + String simpleNumberString(double number) { // Ref: https://stackoverflow.com/questions/55152175/how-to-remove-trailing-zeros-using-dart return number.toStringAsFixed(number.truncateToDouble() == number ? 0 : 1); +} + +Future successTone() async { + + final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; + + if (en) { + final player = AudioCache(); + player.play("sounds/barcode_scan.mp3"); + } +} + +Future failureTone() async { + + final bool en = await InvenTreeSettingsManager().getValue("barcodeSounds", true) as bool; + + if (en) { + final player = AudioCache(); + player.play("sounds/barcode_error.mp3"); + } } \ No newline at end of file diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index 72e6e5e4..46e49cfb 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -16,7 +16,7 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:inventree/l10.dart"; - +import "package:inventree/helpers.dart"; import "package:inventree/api.dart"; import "package:dropdown_search/dropdown_search.dart"; @@ -676,12 +676,31 @@ class _StockItemDisplayState extends RefreshableState { leading: FaIcon(FontAwesomeIcons.barcode, color: COLOR_CLICK), trailing: FaIcon(FontAwesomeIcons.qrcode), onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemBarcodeAssignmentHandler(item))) - ).then((context) { - refresh(); + + var handler = UniqueBarcodeHandler((String hash) { + item.update( + values: { + "uid": hash, + } + ).then((result) { + if (result) { + successTone(); + + showSnackIcon( + L10().barcodeAssigned, + success: true, + icon: FontAwesomeIcons.qrcode + ); + + refresh(); + } + }); }); + + Navigator.push( + context, + MaterialPageRoute(builder: (context) => InvenTreeQRView(handler)) + ); } ) ); From 18479b88e38670c9f7c43d35876c40db58cfeea7 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 12:11:22 +1100 Subject: [PATCH 58/70] Refactor "drawer" - Reduce number of items - Long-pressing the "back" button opens the drawer Ref: https://stackoverflow.com/questions/58095547/flutter-scaffold-ofcontext-opendrawer-doesnt-work --- lib/widget/back.dart | 10 ++-- lib/widget/drawer.dart | 88 ++----------------------------- lib/widget/refreshable_state.dart | 19 +++---- 3 files changed, 18 insertions(+), 99 deletions(-) diff --git a/lib/widget/back.dart b/lib/widget/back.dart index 587a69b3..d93a86d0 100644 --- a/lib/widget/back.dart +++ b/lib/widget/back.dart @@ -7,13 +7,15 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -Widget backButton(BuildContext context) { +import "package:inventree/widget/drawer.dart"; + +Widget backButton(BuildContext context, GlobalKey key) { return GestureDetector( onLongPress: () { - while (Navigator.of(context).canPop()) { - Navigator.of(context).pop(); - } + // Display the menu + key.currentState!.openDrawer(); + print("hello?"); }, child: IconButton( icon: BackButtonIcon(), diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 93a9cdf7..af865ca1 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -28,7 +28,9 @@ class InvenTreeDrawer extends StatelessWidget { void _home() { _closeDrawer(); - Navigator.pushNamedAndRemoveUntil(context, "/", (r) => false); + while (Navigator.of(context).canPop()) { + Navigator.of(context).pop(); + } } void _search() { @@ -37,12 +39,7 @@ class InvenTreeDrawer extends StatelessWidget { _closeDrawer(); - showSearch( - context: context, - delegate: PartSearchDelegate(context) - ); - - //Navigator.push(context, MaterialPageRoute(builder: (context) => SearchWidget())); + // TODO: Open search dialog } /* @@ -56,48 +53,6 @@ class InvenTreeDrawer extends StatelessWidget { scanQrCode(context); } - /* - * Display the top-level PartCategory list - */ - void _showParts() { - if (!InvenTreeAPI().checkConnection(context)) return; - - _closeDrawer(); - Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); - } - - /* - * Display the top-level StockLocation list - */ - void _showStock() { - if (!InvenTreeAPI().checkConnection(context)) return; - _closeDrawer(); - Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); - } - - /* - void _showSuppliers() { - if (!InvenTreeAPI().checkConnection(context)) return; - _closeDrawer(); - - Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"}))); - } - - void _showManufacturers() { - if (!InvenTreeAPI().checkConnection(context)) return; - _closeDrawer(); - - Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"}))); - } - - void _showCustomers() { - if (!InvenTreeAPI().checkConnection(context)) return; - _closeDrawer(); - - Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); - } - */ - /* * Load settings widget */ @@ -114,11 +69,7 @@ class InvenTreeDrawer extends StatelessWidget { context: context, tiles: [ ListTile( - leading: Image.asset( - "assets/image/icon.png", - fit: BoxFit.scaleDown, - width: 30, - ), + leading: FaIcon(FontAwesomeIcons.home), title: Text( L10().appTitle, style: TextStyle(fontWeight: FontWeight.bold), @@ -135,35 +86,6 @@ class InvenTreeDrawer extends StatelessWidget { leading: FaIcon(FontAwesomeIcons.search), onTap: _search, ), - ListTile( - title: Text(L10().parts), - leading: Icon(Icons.category), - onTap: _showParts, - ), - ListTile( - title: Text(L10().stock), - leading: FaIcon(FontAwesomeIcons.boxes), - onTap: _showStock, - ), - - /* - ListTile( - title: Text("Suppliers"), - leading: FaIcon(FontAwesomeIcons.building), - onTap: _showSuppliers, - ), - ListTile( - title: Text("Manufacturers"), - leading: FaIcon(FontAwesomeIcons.industry), - onTap: _showManufacturers, - ), - ListTile( - title: Text("Customers"), - leading: FaIcon(FontAwesomeIcons.users), - onTap: _showCustomers, - ), - */ - ListTile( title: Text(L10().settings), leading: Icon(Icons.settings), diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index 0b8cd2b8..f3e59f3e 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -62,15 +62,6 @@ abstract class RefreshableState extends State { }); } - // Function to construct an appbar (override if needed) - AppBar getAppBar(BuildContext context) { - return AppBar( - title: Text(getAppBarTitle(context)), - actions: getAppBarActions(context), - leading: backButton(context), - ); - } - // Function to construct a drawer (override if needed) Widget getDrawer(BuildContext context) { return InvenTreeDrawer(context); @@ -96,11 +87,15 @@ abstract class RefreshableState extends State { // Save the context for future use _context = context; - + return Scaffold( key: refreshableKey, - appBar: getAppBar(context), - drawer: null, + appBar: AppBar( + title: Text(getAppBarTitle(context)), + actions: getAppBarActions(context), + leading: backButton(context, refreshableKey), + ), + drawer: getDrawer(context), floatingActionButton: getFab(context), body: Builder( builder: (BuildContext context) { From 69c15797e836823cdccc5f7388997987e723d587 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 12:12:16 +1100 Subject: [PATCH 59/70] Linting fixes --- lib/api_form.dart | 2 +- lib/widget/back.dart | 2 -- lib/widget/drawer.dart | 4 ---- lib/widget/refreshable_state.dart | 4 ++-- 4 files changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 799f1864..87acf24c 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -7,7 +7,7 @@ import "package:date_field/date_field.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; -import 'package:inventree/barcode.dart'; +import "package:inventree/barcode.dart"; import "package:inventree/helpers.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; diff --git a/lib/widget/back.dart b/lib/widget/back.dart index d93a86d0..be3ade92 100644 --- a/lib/widget/back.dart +++ b/lib/widget/back.dart @@ -7,8 +7,6 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -import "package:inventree/widget/drawer.dart"; - Widget backButton(BuildContext context, GlobalKey key) { return GestureDetector( diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index af865ca1..427ea955 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -1,12 +1,8 @@ import "package:inventree/api.dart"; import "package:inventree/barcode.dart"; -import "package:inventree/widget/search.dart"; import "package:flutter/material.dart"; import "package:inventree/l10.dart"; -import "package:inventree/widget/category_display.dart"; -import "package:inventree/widget/location_display.dart"; - import "package:inventree/settings/settings.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index f3e59f3e..f7198ed6 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -1,4 +1,4 @@ -import 'package:inventree/widget/back.dart'; +import "package:inventree/widget/back.dart"; import "package:inventree/widget/drawer.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; @@ -87,7 +87,7 @@ abstract class RefreshableState extends State { // Save the context for future use _context = context; - + return Scaffold( key: refreshableKey, appBar: AppBar( From 488df257584b16152872d01d12a2039b011e2334 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 22:26:45 +1100 Subject: [PATCH 60/70] Display link to purchase orders on the "company" page --- lib/inventree/company.dart | 30 ++++++++++++++++++++++++++++++ lib/widget/company_detail.dart | 29 +++++++++++++++++++++++++++++ lib/widget/home.dart | 3 +-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index a749d233..8d89a5a4 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -1,5 +1,9 @@ +import "dart:async"; +import "dart:io"; + import "package:inventree/api.dart"; import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/purchase_order.dart"; /* @@ -44,6 +48,32 @@ class InvenTreeCompany extends InvenTreeModel { bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; + // Request a list of purchase orders against this company + Future> getPurchaseOrders({bool? outstanding}) async { + + Map filters = { + "supplier": "${pk}" + }; + + if (outstanding != null) { + filters["outstanding"] = outstanding ? "true" : "false"; + } + + final List results = await InvenTreePurchaseOrder().list( + filters: filters + ); + + List orders = []; + + for (InvenTreeModel model in results) { + if (model is InvenTreePurchaseOrder) { + orders.add(model); + } + } + + return orders; + } + @override InvenTreeModel createFromJson(Map json) { var company = InvenTreeCompany.fromJson(json); diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index 21b9e380..608dc578 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -2,12 +2,15 @@ import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/purchase_order.dart"; +import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/l10.dart"; + class CompanyDetailWidget extends StatefulWidget { const CompanyDetailWidget(this.company, {Key? key}) : super(key: key); @@ -26,6 +29,8 @@ class _CompanyDetailState extends RefreshableState { final InvenTreeCompany company; + List outstandingOrders = []; + @override String getAppBarTitle(BuildContext context) => L10().company; @@ -60,6 +65,10 @@ class _CompanyDetailState extends RefreshableState { @override Future request() async { await company.reload(); + + if (company.isSupplier) { + outstandingOrders = await company.getPurchaseOrders(outstanding: true); + } } Future editCompany(BuildContext context) async { @@ -145,6 +154,26 @@ class _CompanyDetailState extends RefreshableState { // TODO - Add list of purchase orders tiles.add(Divider()); + + tiles.add( + ListTile( + title: Text(L10().purchaseOrders), + leading: FaIcon(FontAwesomeIcons.shoppingCart, color: COLOR_CLICK), + trailing: Text("${outstandingOrders.length}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget( + filters: { + "supplier": "${company.pk}" + } + ) + ) + ); + } + ) + ); } if (company.isManufacturer) { diff --git a/lib/widget/home.dart b/lib/widget/home.dart index eba54768..486ebc8c 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -82,8 +82,7 @@ class _InvenTreeHomePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget( - ) + builder: (context) => PurchaseOrderListWidget() ) ); } From 98e0106a8127be16871d75c92cabe02c16be75b1 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 23:36:26 +1100 Subject: [PATCH 61/70] Update company detail display page --- lib/inventree/company.dart | 4 ++++ lib/widget/company_detail.dart | 11 +++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 8d89a5a4..59246769 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -48,6 +48,10 @@ class InvenTreeCompany extends InvenTreeModel { bool get isCustomer => (jsondata["is_customer"] ?? false) as bool; + int get partSuppliedCount => (jsondata["parts_supplied"] ?? 0) as int; + + int get partManufacturedCount => (jsondata["parts_manufactured"] ?? 0) as int; + // Request a list of purchase orders against this company Future> getPurchaseOrders({bool? outstanding}) async { diff --git a/lib/widget/company_detail.dart b/lib/widget/company_detail.dart index 608dc578..058249ba 100644 --- a/lib/widget/company_detail.dart +++ b/lib/widget/company_detail.dart @@ -174,6 +174,17 @@ class _CompanyDetailState extends RefreshableState { } ) ); + + // TODO: Display "supplied parts" count (click through to list of supplier parts) + /* + tiles.add( + ListTile( + title: Text(L10().suppliedParts), + leading: FaIcon(FontAwesomeIcons.shapes), + trailing: Text("${company.partSuppliedCount}"), + ) + ); + */ } if (company.isManufacturer) { From 1ca3732a33a751e5d023ee115d550c6857c0ba63 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 23:37:15 +1100 Subject: [PATCH 62/70] Work on refactoring search page - Implement generic "search" count preview - Nice and speedy! --- lib/inventree/model.dart | 28 ++ lib/l10n | 2 +- lib/widget/drawer.dart | 9 +- lib/widget/home.dart | 11 +- lib/widget/search.dart | 580 ++++++++++++++++----------------------- 5 files changed, 276 insertions(+), 354 deletions(-) diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 03e7fc13..61ed3bcb 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -185,6 +185,34 @@ class InvenTreeModel { } + // Return the number of results that would meet a particular "query" + Future count({Map filters = const {}, String search = ""} ) async { + + var params = defaultListFilters(); + + filters.forEach((String key, String value) { + params[key] = value; + }); + + if (search.isNotEmpty) { + params["search"] = search; + } + + // Limit to 1 result, for quick DB access + params["limit"] = "1"; + + var response = await api.get(URL, params: params); + + if (response.isValid()) { + int n = int.tryParse(response.data["count"].toString()) ?? 0; + + print("${URL} -> ${n} results"); + return n; + } else { + return 0; + } +} + Map defaultListFilters() { return {}; } diff --git a/lib/l10n b/lib/l10n index 5f30fef9..d004dc01 160000 --- a/lib/l10n +++ b/lib/l10n @@ -1 +1 @@ -Subproject commit 5f30fef900ea027cd7fe90b2d87a6f02ced315c0 +Subproject commit d004dc013edfc47b5ed94c5b019a013dc5ef444a diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 427ea955..bb983c94 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -5,6 +5,7 @@ import "package:inventree/l10.dart"; import "package:inventree/settings/settings.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/widget/search.dart"; class InvenTreeDrawer extends StatelessWidget { @@ -35,7 +36,12 @@ class InvenTreeDrawer extends StatelessWidget { _closeDrawer(); - // TODO: Open search dialog + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SearchWidget() + ) + ); } /* @@ -59,6 +65,7 @@ class InvenTreeDrawer extends StatelessWidget { @override Widget build(BuildContext context) { + return Drawer( child: ListView( children: ListTile.divideTiles( diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 486ebc8c..c58954e7 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -17,6 +17,7 @@ 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/purchase_order_list.dart"; +import 'package:inventree/widget/search.dart'; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/drawer.dart"; @@ -42,7 +43,15 @@ class _InvenTreeHomePageState extends State { UserProfile? _profile; void _search(BuildContext context) { - // TODO + if (!InvenTreeAPI().checkConnection(context)) return; + + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SearchWidget() + ) + ); + } void _scan(BuildContext context) { diff --git a/lib/widget/search.dart b/lib/widget/search.dart index c209310c..dde18baf 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -1,392 +1,270 @@ +import "dart:async"; +import 'package:inventree/inventree/company.dart'; +import 'package:inventree/inventree/purchase_order.dart'; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; +import 'package:inventree/widget/refreshable_state.dart'; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock_detail.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/l10.dart"; - import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; -import "package:inventree/api.dart"; -// TODO - Refactor duplicate code in this file! - -class PartSearchDelegate extends SearchDelegate { - - PartSearchDelegate(this.context, {Map filters = const {}}) { - - // Copy filter values - for (String key in filters.keys) { - - String? value = filters[key]; - - if (value != null) { - _filters[key] = value; - } - } - } - - final partSearchKey = GlobalKey(); - - BuildContext context; - - // What did we search for last time? - String _cachedQuery = ""; - - bool _searching = false; - - // Custom filters for the part search - Map _filters = {}; +// Widget for performing database-wide search +class SearchWidget extends StatefulWidget { @override - String get searchFieldLabel => L10().searchParts; + _SearchDisplayState createState() => _SearchDisplayState(); - // List of part results - List partResults = []; - - Future search(BuildContext context) async { - - // Search string too short! - if (query.length < 3) { - partResults.clear(); - showResults(context); - return; - } - - if (query == _cachedQuery) { - return; - } - - _cachedQuery = query; - - _searching = true; - - print("Searching..."); - - showResults(context); - - _filters["cascade"] = "true"; - - final results = await InvenTreePart().search(query, filters: _filters); - - partResults.clear(); - - for (int idx = 0; idx < results.length; idx++) { - if (results[idx] is InvenTreePart) { - partResults.add(results[idx] as InvenTreePart); - } - } - - print("Searching complete! Results: ${partResults.length}"); - _searching = false; - - showSnackIcon( - "${partResults.length} ${L10().results}", - success: partResults.isNotEmpty, - icon: FontAwesomeIcons.pollH, - ); - - // 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.backspace), - onPressed: () { - query = ""; - search(context); - }, - ), - IconButton( - icon: FaIcon(FontAwesomeIcons.search), - onPressed: () { - search(context); - } - ), - ]; - } - - @override - Widget buildLeading(BuildContext context) { - return IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - 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 - ), - trailing: Text(part.inStockString), - onTap: () { - InvenTreePart().get(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"); - - if (_searching) { - return progressIndicator(); - } - - search(context); - - if (query.isEmpty) { - return ListTile( - title: Text(L10().queryEnter) - ); - } - - if (query.length < 3) { - return ListTile( - title: Text(L10().queryShort), - subtitle: Text(L10().queryShortDetail) - ); - } - - if (partResults.isEmpty) { - return ListTile( - title: Text(L10().noResults), - subtitle: Text(L10().queryNoResults + " '${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) { - final ThemeData theme = Theme.of(context); - return theme; - } } - -class StockSearchDelegate extends SearchDelegate { - - StockSearchDelegate(this.context, {Map filters = const {}}) { - - // Copy filter values - for (String key in filters.keys) { - - String? value = filters[key]; - - if (value != null) { - _filters[key] = value; - } - } - } - - final stockSearchKey = GlobalKey(); - - final BuildContext context; - - String _cachedQuery = ""; - - bool _searching = false; - - // Custom filters for the stock item search - Map _filters = {}; +class _SearchDisplayState extends RefreshableState { @override - String get searchFieldLabel => L10().searchStock; + String getAppBarTitle(BuildContext context) => L10().search; - // List of StockItem results - List itemResults = []; + final TextEditingController searchController = TextEditingController(); + + Timer? debounceTimer; + + int nPartResults = 0; + + int nCategoryResults = 0; + + int nStockResults = 0; + + int nLocationResults = 0; + + int nSupplierResults = 0; + + int nPurchaseOrderResults = 0; + + // Callback when the text is being edited + // Incorporates a debounce timer to restrict search frequency + void onSearchTextChanged(String text, {bool immediate = false}) { + + if (debounceTimer?.isActive ?? false) { + debounceTimer!.cancel(); + } + + if (immediate) { + search(text); + } else { + debounceTimer = Timer(Duration(milliseconds: 250), () { + search(text); + }); + } + + } + + Future search(String term) async { + + if (term.isEmpty) { + setState(() { + // Do not search on an empty string + nPartResults = 0; + nCategoryResults = 0; + nStockResults = 0; + nLocationResults = 0; + nSupplierResults = 0; + nPurchaseOrderResults = 0; + }); - Future search(BuildContext context) async { - // Search string too short! - if (query.length < 3) { - itemResults.clear(); - showResults(context); return; } - if (query == _cachedQuery) { - return; + // Search parts + InvenTreePart().count( + search: term + ).then((int n) { + setState(() { + nPartResults = n; + }); + }); + + // Search part categories + InvenTreePartCategory().count( + search: term, + ).then((int n) { + setState(() { + nCategoryResults = n; + }); + }); + + // Search stock items + InvenTreeStockItem().count( + search: term + ).then((int n) { + setState(() { + nStockResults = n; + }); + }); + + // Search stock locations + InvenTreeStockLocation().count( + search: term + ).then((int n) { + setState(() { + nLocationResults = n; + }); + }); + + // Search suppliers + InvenTreeCompany().count( + search: term, + filters: { + "is_supplier": "true", + }, + ).then((int n) { + setState(() { + nSupplierResults = n; + }); + }); + + // Search purchase orders + InvenTreePurchaseOrder().count( + search: term, + filters: { + "outstanding": "true" + } + ).then((int n) { + setState(() { + nPurchaseOrderResults = n; + }); + }); + + } + + List _tiles(BuildContext context) { + + List tiles = []; + + // Search input + tiles.add( + InputDecorator( + decoration: InputDecoration( + ), + child: ListTile( + title: TextField( + readOnly: false, + controller: searchController, + onChanged: (String text) { + onSearchTextChanged(text); + }, + ), + trailing: IconButton( + icon: FaIcon(FontAwesomeIcons.backspace, color: Colors.red), + onPressed: () { + searchController.clear(); + onSearchTextChanged("", immediate: true); + }, + ), + ) + ) + ); + + List results = []; + + // Part Results + if (nPartResults > 0) { + results.add( + ListTile( + title: Text(L10().parts), + leading: FaIcon(FontAwesomeIcons.shapes), + trailing: Text("${nPartResults}"), + onTap: () { + // Show part results + } + ) + ); } - _cachedQuery = query; + // Part Category Results + if (nCategoryResults > 0) { + results.add( + ListTile( + title: Text(L10().partCategories), + leading: FaIcon(FontAwesomeIcons.sitemap), + trailing: Text("${nCategoryResults}"), + ) + ); + } - _searching = true; + // Stock Item Results + if (nStockResults > 0) { + results.add( + ListTile( + title: Text(L10().stockItems), + leading: FaIcon(FontAwesomeIcons.boxes), + trailing: Text("${nStockResults}"), + ) + ); + } - print("Searching..."); + // Stock location results + if (nLocationResults > 0) { + results.add( + ListTile( + title: Text(L10().stockLocations), + leading: FaIcon(FontAwesomeIcons.mapMarkerAlt), + trailing: Text("${nLocationResults}"), + ) + ); + } - showResults(context); + // Suppliers + if (nSupplierResults > 0) { + results.add( + ListTile( + title: Text(L10().suppliers), + leading: FaIcon(FontAwesomeIcons.building), + trailing: Text("${nSupplierResults}") + ) + ); + } - // Enable cascading part search by default - _filters["cascade"] = "true"; + // Purchase orders + if (nPurchaseOrderResults > 0) { + results.add( + ListTile( + title: Text(L10().purchaseOrders), + leading: FaIcon(FontAwesomeIcons.shoppingCart), + trailing: Text("${nPurchaseOrderResults}"), + ) + ); + } - final results = await InvenTreeStockItem().search(query, filters: _filters); - - itemResults.clear(); - - for (int idx = 0; idx < results.length; idx++) { - if (results[idx] is InvenTreeStockItem) { - itemResults.add(results[idx] as InvenTreeStockItem); + if (results.isEmpty) { + tiles.add( + ListTile( + title: Text(L10().queryNoResults), + leading: FaIcon(FontAwesomeIcons.search), + ) + ); + } else { + for (Widget result in results) { + tiles.add(result); } } - _searching = false; + return tiles; - showSnackIcon( - "${itemResults.length} ${L10().results}", - success: itemResults.isNotEmpty, - icon: FontAwesomeIcons.pollH, - ); - - showSuggestions(context); - showResults(context); } @override - List buildActions(BuildContext context) { - return [ - IconButton( - icon: FaIcon(FontAwesomeIcons.backspace), - onPressed: () { - query = ""; - search(context); - }, - ), - IconButton( - icon: FaIcon(FontAwesomeIcons.search), - onPressed: () { - search(context); - } - ), - ]; - } - - @override - Widget buildLeading(BuildContext context) { - return IconButton( - icon: Icon(Icons.arrow_back), - onPressed: () { - close(context, null); - } + Widget getBody(BuildContext context) { + return Center( + child: ListView( + children: ListTile.divideTiles( + context: context, + tiles: _tiles(context), + ).toList() + ) ); } - - Widget _itemResult(BuildContext context, int index) { - - InvenTreeStockItem item = itemResults[index]; - - return ListTile( - title: Text(item.partName), - subtitle: Text(item.locationName), - leading: InvenTreeAPI().getImage( - item.partThumbnail, - width: 40, - height: 40, - ), - trailing: Text(item.serialOrQuantityDisplay()), - onTap: () { - InvenTreeStockItem().get(item.pk).then((var it) { - if (it is InvenTreeStockItem) { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => StockDetailWidget(it)) - ); - } - }); - } - ); - } - - @override - Widget buildResults(BuildContext context) { - - search(context); - - if (_searching) { - return progressIndicator(); - } - - search(context); - - if (query.isEmpty) { - return ListTile( - title: Text(L10().queryEnter) - ); - } - - if (query.length < 3) { - return ListTile( - title: Text(L10().queryShort), - subtitle: Text(L10().queryShortDetail) - ); - } - - if (itemResults.isEmpty) { - return ListTile( - title: Text(L10().noResults), - subtitle: Text(L10().queryNoResults + " '${query}'") - ); - } - - return ListView.separated( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - separatorBuilder: (_, __) => const Divider(height: 3), - itemBuilder: _itemResult, - itemCount: itemResults.length, - ); - } - - @override - Widget buildSuggestions(BuildContext context) { - // TODO - Implement - return Column(); - } - - // Ensure the search theme matches the app theme - @override - ThemeData appBarTheme(BuildContext context) { - final ThemeData theme = Theme.of(context); - return theme; - } -} \ No newline at end of file +} From b656eb7b4375b9ae4e7f04ed79b373c22ae6cbc6 Mon Sep 17 00:00:00 2001 From: Oliver Date: Sun, 3 Oct 2021 23:47:59 +1100 Subject: [PATCH 63/70] Show supplier results --- lib/inventree/model.dart | 21 ++++++++++++++++++--- lib/widget/search.dart | 30 +++++++++++++++++++++++------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 61ed3bcb..c65a551e 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -186,7 +186,7 @@ class InvenTreeModel { } // Return the number of results that would meet a particular "query" - Future count({Map filters = const {}, String search = ""} ) async { + Future count({Map filters = const {}, String searchQuery = ""} ) async { var params = defaultListFilters(); @@ -194,8 +194,8 @@ class InvenTreeModel { params[key] = value; }); - if (search.isNotEmpty) { - params["search"] = search; + if (searchQuery.isNotEmpty) { + params["search"] = searchQuery; } // Limit to 1 result, for quick DB access @@ -382,6 +382,21 @@ class InvenTreeModel { params["limit"] = "${limit}"; params["offset"] = "${offset}"; + /* Special case: "original_search": + * - We may wish to provide an original "query" which is augmented by the user + * - Thus, "search" and "original_search" may both be provided + * - In such a case, we want to concatenate them together + */ + if (params.containsKey("original_search")) { + + String search = params["search"] ?? ""; + String original = params["original_search"] ?? ""; + + params["search"] = "${search} ${original}"; + + params.remove("original_search"); + } + var response = await api.get(URL, params: params); if (!response.isValid()) { diff --git a/lib/widget/search.dart b/lib/widget/search.dart index dde18baf..ca838dcd 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -80,7 +80,7 @@ class _SearchDisplayState extends RefreshableState { // Search parts InvenTreePart().count( - search: term + searchQuery: term ).then((int n) { setState(() { nPartResults = n; @@ -89,7 +89,7 @@ class _SearchDisplayState extends RefreshableState { // Search part categories InvenTreePartCategory().count( - search: term, + searchQuery: term, ).then((int n) { setState(() { nCategoryResults = n; @@ -98,7 +98,7 @@ class _SearchDisplayState extends RefreshableState { // Search stock items InvenTreeStockItem().count( - search: term + searchQuery: term ).then((int n) { setState(() { nStockResults = n; @@ -107,7 +107,7 @@ class _SearchDisplayState extends RefreshableState { // Search stock locations InvenTreeStockLocation().count( - search: term + searchQuery: term ).then((int n) { setState(() { nLocationResults = n; @@ -116,7 +116,7 @@ class _SearchDisplayState extends RefreshableState { // Search suppliers InvenTreeCompany().count( - search: term, + searchQuery: term, filters: { "is_supplier": "true", }, @@ -128,7 +128,7 @@ class _SearchDisplayState extends RefreshableState { // Search purchase orders InvenTreePurchaseOrder().count( - search: term, + searchQuery: term, filters: { "outstanding": "true" } @@ -168,6 +168,8 @@ class _SearchDisplayState extends RefreshableState { ) ); + String query = searchController.text; + List results = []; // Part Results @@ -223,7 +225,21 @@ class _SearchDisplayState extends RefreshableState { ListTile( title: Text(L10().suppliers), leading: FaIcon(FontAwesomeIcons.building), - trailing: Text("${nSupplierResults}") + trailing: Text("${nSupplierResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CompanyListWidget( + L10().suppliers, + { + "is_supplier": "true", + "original_search": query + } + ) + ) + ); + }, ) ); } From 581d182464cae3bbc053c878de3ca0cc0e8c226d Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 00:14:12 +1100 Subject: [PATCH 64/70] Refactor StockList --- lib/inventree/model.dart | 2 +- lib/inventree/stock.dart | 3 +- lib/widget/location_display.dart | 166 +-------------------- lib/widget/part_detail.dart | 5 +- lib/widget/purchase_order_detail.dart | 3 +- lib/widget/purchase_order_list.dart | 8 +- lib/widget/search.dart | 28 ++++ lib/widget/stock_list.dart | 201 ++++++++++++++++++++++++++ 8 files changed, 244 insertions(+), 172 deletions(-) create mode 100644 lib/widget/stock_list.dart diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index c65a551e..d894ae68 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -392,7 +392,7 @@ class InvenTreeModel { String search = params["search"] ?? ""; String original = params["original_search"] ?? ""; - params["search"] = "${search} ${original}"; + params["search"] = "${search} ${original}".trim(); params.remove("original_search"); } diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 45606a2d..29391b26 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -149,7 +149,8 @@ class InvenTreeStockItem extends InvenTreeModel { "part_detail": "true", "location_detail": "true", "supplier_detail": "true", - "cascade": "false" + "cascade": "false", + "in_stock": "true", }; } diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 26950a34..17ace857 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -16,6 +16,7 @@ import "package:flutter/material.dart"; import "package:flutter/foundation.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/widget/stock_list.dart'; class LocationDisplayWidget extends StatefulWidget { @@ -286,7 +287,7 @@ class _LocationDisplayState extends RefreshableState { children: detailTiles(), ); case 1: - return PaginatedStockList(filters); + return PaginatedStockItemList(filters); case 2: return ListView( children: ListTile.divideTiles( @@ -467,166 +468,3 @@ class SublocationList extends StatelessWidget { ); } } - -/* - * Widget for displaying a list of stock items within a stock location. - * - * Users server-side pagination for snappy results - */ - -class PaginatedStockList extends StatefulWidget { - - const PaginatedStockList(this.filters); - - final Map filters; - - @override - _PaginatedStockListState createState() => _PaginatedStockListState(filters); -} - - -class _PaginatedStockListState extends State { - - _PaginatedStockListState(this.filters); - - static const _pageSize = 25; - - String _searchTerm = ""; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); - - super.initState(); - } - - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = "${_searchTerm}"; - - // Do we include stock items from sub-locations? - final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of stock item objects - List items = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeStockItem) { - items.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(items); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(items, nextPageKey); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void _openItem(BuildContext context, int pk) { - InvenTreeStockItem().get(pk).then((var item) { - if (item is InvenTreeStockItem) { - Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); - } - }); - } - - Widget _buildItem(BuildContext context, InvenTreeStockItem item) { - return ListTile( - title: Text("${item.partName}"), - subtitle: Text("${item.locationPathString}"), - leading: InvenTreeAPI().getImage( - item.partThumbnail, - width: 40, - height: 40, - ), - trailing: Text("${item.displayQuantity}", - style: TextStyle( - fontWeight: FontWeight.bold, - color: item.statusColor, - ), - ), - onTap: () { - _openItem(context, item.pk); - }, - ); - } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build (BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - // TODO - Search input - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildItem(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget("No stock items found"); - } - ), - separatorBuilder: (context, item) => const Divider(height: 1), - ) - ] - ) - ) - ] - ); - } -} diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 54d2e4c5..254e1a53 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -18,6 +18,7 @@ import "package:inventree/widget/part_image_widget.dart"; import "package:inventree/widget/stock_detail.dart"; import "package:inventree/widget/location_display.dart"; +import 'package:inventree/widget/stock_list.dart'; class PartDetailWidget extends StatefulWidget { @@ -494,7 +495,9 @@ class _PartDisplayState extends RefreshableState { ), ); case 1: - return PaginatedStockList({"part": "${part.pk}"}); + return PaginatedStockItemList( + {"part": "${part.pk}"} + ); case 2: return Center( child: ListView( diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 9a5cd703..4275bb89 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -13,6 +13,7 @@ import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/snacks.dart"; +import 'package:inventree/widget/stock_list.dart'; import "package:one_context/one_context.dart"; @@ -351,7 +352,7 @@ class _PurchaseOrderDetailState extends RefreshableState filters; @@ -55,7 +55,7 @@ class _PaginatedPurchaseOrderList extends StatefulWidget { } -class _PaginatedPurchaseOrderListState extends State<_PaginatedPurchaseOrderList> { +class _PaginatedPurchaseOrderListState extends State { _PaginatedPurchaseOrderListState(this.filters, this.onTotalChanged); diff --git a/lib/widget/search.dart b/lib/widget/search.dart index ca838dcd..c522c747 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -4,6 +4,7 @@ import 'package:inventree/inventree/company.dart'; import 'package:inventree/inventree/purchase_order.dart'; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/progress.dart"; +import 'package:inventree/widget/purchase_order_list.dart'; import 'package:inventree/widget/refreshable_state.dart'; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock_detail.dart"; @@ -13,6 +14,9 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; +import 'package:inventree/widget/stock_list.dart'; + +import 'company_list.dart'; // Widget for performing database-wide search @@ -204,6 +208,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().stockItems), leading: FaIcon(FontAwesomeIcons.boxes), trailing: Text("${nStockResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StockItemList( + { + "original_search": query, + } + ) + ) + ); + }, ) ); } @@ -251,6 +267,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().purchaseOrders), leading: FaIcon(FontAwesomeIcons.shoppingCart), trailing: Text("${nPurchaseOrderResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget( + filters: { + "original_search": query + } + ) + ) + ); + }, ) ); } diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart new file mode 100644 index 00000000..f3affc50 --- /dev/null +++ b/lib/widget/stock_list.dart @@ -0,0 +1,201 @@ + +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; + +import "package:inventree/l10.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/widget/stock_detail.dart"; + +import "package:inventree/api.dart"; + +class StockItemList extends StatefulWidget { + + const StockItemList(this.filters); + + final Map filters; + + @override + _StockListState createState() => _StockListState(filters); +} + + +class _StockListState extends RefreshableState { + + _StockListState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().purchaseOrders; + + @override + Widget getBody(BuildContext context) { + return PaginatedStockItemList(filters); + } +} + +class PaginatedStockItemList extends StatefulWidget { + + const PaginatedStockItemList(this.filters); + + final Map filters; + + @override + _PaginatedStockItemListState createState() => _PaginatedStockItemListState(filters); + +} + + +class _PaginatedStockItemListState extends State { + + _PaginatedStockItemListState(this.filters); + + static const _pageSize = 25; + + String _searchTerm = ""; + + final Map filters; + + final PagingController _pagingController = PagingController(firstPageKey: 0); + + @override + String getAppbarTitle(BuildContext context) => L10().stockItems; + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + int resultCount = 0; + + Future _fetchPage(int pageKey) async { + try { + + Map params = filters; + + params["search"] = "${_searchTerm}"; + + // Do we include stock items from sub-locations? + final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); + + params["cascade"] = "${cascade}"; + + final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); + + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + // Construct a list of stock item objects + List items = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreeStockItem) { + items.add(result); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(items); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(items, nextPageKey); + } + + setState(() { + resultCount = pageCount; + }); + + } catch (error, stackTrace) { + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void _openItem(BuildContext context, int pk) { + InvenTreeStockItem().get(pk).then((var item) { + if (item is InvenTreeStockItem) { + Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item))); + } + }); + } + + Widget _buildItem(BuildContext context, InvenTreeStockItem item) { + return ListTile( + title: Text("${item.partName}"), + subtitle: Text("${item.locationPathString}"), + leading: InvenTreeAPI().getImage( + item.partThumbnail, + width: 40, + height: 40, + ), + trailing: Text("${item.displayQuantity}", + style: TextStyle( + fontWeight: FontWeight.bold, + color: item.statusColor, + ), + ), + onTap: () { + _openItem(context, item.pk); + }, + ); + } + + final TextEditingController searchController = TextEditingController(); + + void updateSearchTerm() { + _searchTerm = searchController.text; + _pagingController.refresh(); + } + + @override + Widget build (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + // TODO - Search input + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return _buildItem(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget("No stock items found"); + } + ), + separatorBuilder: (context, item) => const Divider(height: 1), + ) + ] + ) + ) + ] + ); + } +} \ No newline at end of file From 6dad1f2b2537141a28aeefca4c6769311e58d0b5 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 00:20:06 +1100 Subject: [PATCH 65/70] Refactor part list --- lib/widget/category_display.dart | 169 +------------------------- lib/widget/part_list.dart | 201 +++++++++++++++++++++++++++++++ lib/widget/search.dart | 14 ++- 3 files changed, 214 insertions(+), 170 deletions(-) create mode 100644 lib/widget/part_list.dart diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 3fa0d7d5..86856156 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -4,6 +4,7 @@ import "package:inventree/app_colors.dart"; import "package:inventree/app_settings.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/sentry.dart"; +import 'package:inventree/widget/part_list.dart'; import "package:inventree/widget/progress.dart"; import "package:inventree/l10.dart"; @@ -387,171 +388,3 @@ class SubcategoryList extends StatelessWidget { itemBuilder: _build, itemCount: _categories.length); } } - - -/* - * Widget for displaying a list of Part objects within a PartCategory display. - * - * Uses server-side pagination for snappy results - */ - -class PaginatedPartList extends StatefulWidget { - - const PaginatedPartList(this.filters, {this.onTotalChanged}); - - final Map filters; - - final Function(int)? onTotalChanged; - - @override - _PaginatedPartListState createState() => _PaginatedPartListState(filters, onTotalChanged); -} - - -class _PaginatedPartListState extends State { - - _PaginatedPartListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; - - String _searchTerm = ""; - - Function(int)? onTotalChanged; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); - - super.initState(); - } - - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = _searchTerm; - - final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params); - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of part objects - List parts = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreePart) { - parts.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(parts); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(parts, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void _openPart(BuildContext context, int pk) { - // Attempt to load the part information - InvenTreePart().get(pk).then((var part) { - if (part is InvenTreePart) { - - Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); - } - }); - } - - Widget _buildPart(BuildContext context, InvenTreePart part) { - return ListTile( - title: Text(part.fullname), - subtitle: Text("${part.description}"), - trailing: Text("${part.inStockString}"), - leading: InvenTreeAPI().getImage( - part.thumbnail, - width: 40, - height: 40, - ), - onTap: () { - _openPart(context, part.pk); - }, - ); - } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildPart(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().partNoResults); - }, - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } -} diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart new file mode 100644 index 00000000..88725877 --- /dev/null +++ b/lib/widget/part_list.dart @@ -0,0 +1,201 @@ +import 'package:flutter/material.dart'; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:inventree/inventree/part.dart'; +import 'package:inventree/inventree/sentry.dart'; +import 'package:inventree/widget/paginator.dart'; +import 'package:inventree/widget/part_detail.dart'; +import 'package:inventree/widget/refreshable_state.dart'; + +import '../api.dart'; +import '../app_settings.dart'; +import '../l10.dart'; + + +class PartList extends StatefulWidget { + + const PartList(this.filters); + + final Map filters; + + @override + _PartListState createState() => _PartListState(filters); +} + + +class _PartListState extends RefreshableState { + + _PartListState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().parts; + + @override + Widget getBody(BuildContext context) { + return PaginatedPartList(filters); + } + +} + + +class PaginatedPartList extends StatefulWidget { + + const PaginatedPartList(this.filters, {this.onTotalChanged}); + + final Map filters; + + final Function(int)? onTotalChanged; + + @override + _PaginatedPartListState createState() => _PaginatedPartListState(filters, onTotalChanged); +} + + +class _PaginatedPartListState extends State { + + _PaginatedPartListState(this.filters, this.onTotalChanged); + + static const _pageSize = 25; + + String _searchTerm = ""; + + Function(int)? onTotalChanged; + + final Map filters; + + final PagingController _pagingController = PagingController(firstPageKey: 0); + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + int resultCount = 0; + + Future _fetchPage(int pageKey) async { + try { + + Map params = filters; + + params["search"] = _searchTerm; + + final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); + + params["cascade"] = "${cascade}"; + + final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params); + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + // Construct a list of part objects + List parts = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreePart) { + parts.add(result); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(parts); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(parts, nextPageKey); + } + + if (onTotalChanged != null) { + onTotalChanged!(pageCount); + } + + setState(() { + resultCount = pageCount; + }); + + } catch (error, stackTrace) { + print("Error! - ${error.toString()}"); + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void _openPart(BuildContext context, int pk) { + // Attempt to load the part information + InvenTreePart().get(pk).then((var part) { + if (part is InvenTreePart) { + + Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part))); + } + }); + } + + Widget _buildPart(BuildContext context, InvenTreePart part) { + return ListTile( + title: Text(part.fullname), + subtitle: Text("${part.description}"), + trailing: Text("${part.inStockString}"), + leading: InvenTreeAPI().getImage( + part.thumbnail, + width: 40, + height: 40, + ), + onTap: () { + _openPart(context, part.pk); + }, + ); + } + + final TextEditingController searchController = TextEditingController(); + + void updateSearchTerm() { + + _searchTerm = searchController.text; + _pagingController.refresh(); + } + + @override + Widget build(BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return _buildPart(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget(L10().partNoResults); + }, + ), + separatorBuilder: (context, index) => const Divider(height: 1), + ) + ], + ) + ) + ], + ); + } +} \ No newline at end of file diff --git a/lib/widget/search.dart b/lib/widget/search.dart index c522c747..b1e93b47 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -3,6 +3,7 @@ import "dart:async"; import 'package:inventree/inventree/company.dart'; import 'package:inventree/inventree/purchase_order.dart'; import "package:inventree/widget/part_detail.dart"; +import 'package:inventree/widget/part_list.dart'; import "package:inventree/widget/progress.dart"; import 'package:inventree/widget/purchase_order_list.dart'; import 'package:inventree/widget/refreshable_state.dart'; @@ -161,7 +162,7 @@ class _SearchDisplayState extends RefreshableState { onSearchTextChanged(text); }, ), - trailing: IconButton( + leading: IconButton( icon: FaIcon(FontAwesomeIcons.backspace, color: Colors.red), onPressed: () { searchController.clear(); @@ -184,7 +185,16 @@ class _SearchDisplayState extends RefreshableState { leading: FaIcon(FontAwesomeIcons.shapes), trailing: Text("${nPartResults}"), onTap: () { - // Show part results + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PartList( + { + "original_search": query + } + ) + ) + ); } ) ); From d08a94ac2cad0fb707c81494566986625e82b772 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 08:08:07 +1100 Subject: [PATCH 66/70] Refactor paginated search widgets - Implement a base class - Override specific members --- lib/widget/company_list.dart | 126 +++--------------------- lib/widget/paginator.dart | 139 ++++++++++++++++++++++++++ lib/widget/part_list.dart | 145 +++++----------------------- lib/widget/purchase_order_list.dart | 140 +++------------------------ lib/widget/stock_list.dart | 129 ++++--------------------- 5 files changed, 206 insertions(+), 473 deletions(-) diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index 948cf25a..6d2a3f4e 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -5,6 +5,7 @@ import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/api.dart"; import "package:inventree/inventree/company.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; @@ -56,96 +57,25 @@ class PaginatedCompanyList extends StatefulWidget { final Function(int)? onTotalChanged; @override - _CompanyListState createState() => _CompanyListState(filters, onTotalChanged); + _CompanyListState createState() => _CompanyListState(filters); } -class _CompanyListState extends State { +class _CompanyListState extends PaginatedSearchState { - _CompanyListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; + _CompanyListState(Map filters) : super(filters); - String _searchTerm = ""; - - Function(int)? onTotalChanged; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - final TextEditingController searchController = TextEditingController(); - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); - - super.initState(); + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreeCompany().listPaginated(limit, offset, filters: params); + + return page; } - + @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } - - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - Map params = filters; + Widget buildItem(BuildContext context, InvenTreeModel model) { - params["search"] = _searchTerm; - - final page = await InvenTreeCompany().listPaginated( - _pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - List companies = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeCompany) { - companies.add(result); - } else { - print(result.jsondata); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(companies); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(companies, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - Widget _buildCompany(BuildContext context, InvenTreeCompany company) { + InvenTreeCompany company = model as InvenTreeCompany; return ListTile( title: Text(company.name), @@ -160,36 +90,4 @@ class _CompanyListState extends State { }, ); } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildCompany(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().companyNoResults); - } - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } - } \ No newline at end of file diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 6d5eba0c..d46ec19d 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -1,8 +1,147 @@ import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; +import 'package:inventree/inventree/model.dart'; +import 'package:inventree/inventree/sentry.dart'; import "package:inventree/l10.dart"; +class PaginatedSearchState extends State { + + PaginatedSearchState(this.filters); + + final Map filters; + + static const _pageSize = 25; + + // Search query term + String searchTerm = ""; + + int resultCount = 0; + + // Text controller + final TextEditingController searchController = TextEditingController(); + + // Pagination controller + final PagingController _pagingController = PagingController(firstPageKey: 0); + + @override + void initState() { + _pagingController.addPageRequestListener((pageKey) { + _fetchPage(pageKey); + }); + + super.initState(); + } + + @override + void dispose() { + _pagingController.dispose(); + super.dispose(); + } + + Future requestPage(int limit, int offset, Map params) async { + + print("Blank request page"); + // Default implementation returns null - must be overridden + return null; + } + + Future _fetchPage(int pageKey) async { + try { + Map params = filters; + + params["search"] = "${searchTerm}"; + + final page = await requestPage( + _pageSize, + pageKey, + params + ); + + int pageLength = page?.length ?? 0; + int pageCount = page?.count ?? 0; + + final isLastPage = pageLength < _pageSize; + + List items = []; + + if (page != null) { + for (var result in page.results) { + if (result is InvenTreeModel) { + items.add(result); + } + } + } + + if (isLastPage) { + _pagingController.appendLastPage(items); + } else { + final int nextPageKey = pageKey + pageLength; + _pagingController.appendPage(items, nextPageKey); + } + + setState(() { + resultCount = pageCount; + }); + } catch (error, stackTrace) { + _pagingController.error = error; + + sentryReportError(error, stackTrace); + } + } + + void updateSearchTerm() { + searchTerm = searchController.text; + _pagingController.refresh(); + } + + Widget buildItem(BuildContext context, InvenTreeModel item) { + + // This method must be overridden by the child class + return ListTile( + title: Text("*** UNIMPLEMENTED ***"), + subtitle: Text("*** buildItem() is unimplemented for this widget!"), + ); + } + + String get noResultsText => L10().noResults; + + @override + Widget build (BuildContext context) { + return Column( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), + Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: ClampingScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + // TODO - Search input + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + return buildItem(context, item); + }, + noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget(noResultsText); + } + ), + separatorBuilder: (context, item) => const Divider(height: 1), + ) + ] + ) + ) + ] + ); + } + +} + + class PaginatedSearchWidget extends StatelessWidget { const PaginatedSearchWidget(this.controller, this.onChanged, this.results); diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index 88725877..fc4635f9 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -1,14 +1,15 @@ -import 'package:flutter/material.dart'; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/sentry.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/part_detail.dart'; -import 'package:inventree/widget/refreshable_state.dart'; +import "package:flutter/material.dart"; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/inventree/model.dart'; +import "package:inventree/inventree/part.dart"; +import "package:inventree/inventree/sentry.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; -import '../api.dart'; -import '../app_settings.dart'; -import '../l10.dart'; +import "package:inventree/api.dart"; +import "package:inventree/app_settings.dart"; +import "package:inventree/l10.dart"; class PartList extends StatefulWidget { @@ -52,86 +53,21 @@ class PaginatedPartList extends StatefulWidget { } -class _PaginatedPartListState extends State { +class _PaginatedPartListState extends PaginatedSearchState { - _PaginatedPartListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; - - String _searchTerm = ""; + _PaginatedPartListState(Map filters, this.onTotalChanged) : super(filters); Function(int)? onTotalChanged; - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + Future requestPage(int limit, int offset, Map params) async { + final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); - super.initState(); - } + params["cascade"] = "${cascade}"; - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + final page = await InvenTreePart().listPaginated(limit, offset, filters: params); - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = _searchTerm; - - final bool cascade = await InvenTreeSettingsManager().getBool("partSubcategory", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params); - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of part objects - List parts = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreePart) { - parts.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(parts); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(parts, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } + return page; } void _openPart(BuildContext context, int pk) { @@ -144,7 +80,11 @@ class _PaginatedPartListState extends State { }); } - Widget _buildPart(BuildContext context, InvenTreePart part) { + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreePart part = model as InvenTreePart; + return ListTile( title: Text(part.fullname), subtitle: Text("${part.description}"), @@ -159,43 +99,4 @@ class _PaginatedPartListState extends State { }, ); } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildPart(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().partNoResults); - }, - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } } \ No newline at end of file diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 67633221..0c284cfc 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -3,6 +3,7 @@ import "package:flutter/material.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/inventree/company.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/purchase_order_detail.dart"; @@ -43,117 +44,35 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters; - final Function(int)? onTotalChanged; - @override - _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters, onTotalChanged); + _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(filters); } -class _PaginatedPurchaseOrderListState extends State { +class _PaginatedPurchaseOrderListState extends PaginatedSearchState { - _PaginatedPurchaseOrderListState(this.filters, this.onTotalChanged); - - static const _pageSize = 25; - - String _searchTerm = ""; - - Function(int)? onTotalChanged; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); - - final TextEditingController searchController = TextEditingController(); + _PaginatedPurchaseOrderListState(Map filters) : super(filters); @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + Future requestPage(int limit, int offset, Map params) async { + + params["outstanding"] = "true"; + + final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); + + return page; - super.initState(); } @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + Widget buildItem(BuildContext context, InvenTreeModel model) { - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - Map params = {}; - - params["search"] = _searchTerm; - - // Only return results for open purchase orders - params["outstanding"] = "true"; - - // Copy across provided filters - for (String key in filters.keys) { - params[key] = filters[key] ?? ""; - } - - final page = await InvenTreePurchaseOrder().listPaginated( - _pageSize, - pageKey, - filters: params - ); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - List orders = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreePurchaseOrder) { - orders.add(result); - } else { - print("Result is not valid PurchaseOrder:"); - print(result.jsondata); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(orders); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(orders, nextPageKey); - } - - if (onTotalChanged != null) { - onTotalChanged!(pageCount); - } - - setState(() { - resultCount = pageCount; - }); - } catch (error, stackTrace) { - print("Error! - ${error.toString()}"); - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } - } - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - Widget _buildOrder(BuildContext context, InvenTreePurchaseOrder order) { + InvenTreePurchaseOrder order = model as InvenTreePurchaseOrder; InvenTreeCompany? supplier = order.supplier; @@ -176,35 +95,4 @@ class _PaginatedPurchaseOrderListState extends State }, ); } - - @override - Widget build(BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildOrder(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(L10().companyNoResults); - } - ), - separatorBuilder: (context, index) => const Divider(height: 1), - ) - ], - ) - ) - ], - ); - } } \ No newline at end of file diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart index f3affc50..96040860 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock_list.dart @@ -2,6 +2,7 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; +import 'package:inventree/inventree/model.dart'; import "package:inventree/inventree/sentry.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/widget/paginator.dart"; @@ -51,84 +52,25 @@ class PaginatedStockItemList extends StatefulWidget { } -class _PaginatedStockItemListState extends State { +class _PaginatedStockItemListState extends PaginatedSearchState { - _PaginatedStockItemListState(this.filters); - - static const _pageSize = 25; - - String _searchTerm = ""; - - final Map filters; - - final PagingController _pagingController = PagingController(firstPageKey: 0); + _PaginatedStockItemListState(Map filters) : super(filters); @override - String getAppbarTitle(BuildContext context) => L10().stockItems; + Future requestPage(int limit, int offset, Map params) async { - @override - void initState() { - _pagingController.addPageRequestListener((pageKey) { - _fetchPage(pageKey); - }); + // Do we include stock items from sub-locations? + final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - super.initState(); - } + params["cascade"] = "${cascade}"; - @override - void dispose() { - _pagingController.dispose(); - super.dispose(); - } + final page = await InvenTreeStockItem().listPaginated( + limit, + offset, + filters: params + ); - int resultCount = 0; - - Future _fetchPage(int pageKey) async { - try { - - Map params = filters; - - params["search"] = "${_searchTerm}"; - - // Do we include stock items from sub-locations? - final bool cascade = await InvenTreeSettingsManager().getBool("stockSublocation", true); - - params["cascade"] = "${cascade}"; - - final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params); - - int pageLength = page?.length ?? 0; - int pageCount = page?.count ?? 0; - - final isLastPage = pageLength < _pageSize; - - // Construct a list of stock item objects - List items = []; - - if (page != null) { - for (var result in page.results) { - if (result is InvenTreeStockItem) { - items.add(result); - } - } - } - - if (isLastPage) { - _pagingController.appendLastPage(items); - } else { - final int nextPageKey = pageKey + pageLength; - _pagingController.appendPage(items, nextPageKey); - } - - setState(() { - resultCount = pageCount; - }); - - } catch (error, stackTrace) { - _pagingController.error = error; - - sentryReportError(error, stackTrace); - } + return page; } void _openItem(BuildContext context, int pk) { @@ -139,7 +81,11 @@ class _PaginatedStockItemListState extends State { }); } - Widget _buildItem(BuildContext context, InvenTreeStockItem item) { + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreeStockItem item = model as InvenTreeStockItem; + return ListTile( title: Text("${item.partName}"), subtitle: Text("${item.locationPathString}"), @@ -159,43 +105,4 @@ class _PaginatedStockItemListState extends State { }, ); } - - final TextEditingController searchController = TextEditingController(); - - void updateSearchTerm() { - _searchTerm = searchController.text; - _pagingController.refresh(); - } - - @override - Widget build (BuildContext context) { - return Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - PaginatedSearchWidget(searchController, updateSearchTerm, resultCount), - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: ClampingScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - // TODO - Search input - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - return _buildItem(context, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget("No stock items found"); - } - ), - separatorBuilder: (context, item) => const Divider(height: 1), - ) - ] - ) - ) - ] - ); - } } \ No newline at end of file From cf3226da6f6fb4e321625810c78cdd7d44abe28f Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 08:20:32 +1100 Subject: [PATCH 67/70] Add new list widgets - PartCategory - StockLocation --- lib/widget/category_list.dart | 85 ++++++++++++++++++++++++++++++++++ lib/widget/location_list.dart | 87 +++++++++++++++++++++++++++++++++++ lib/widget/search.dart | 26 +++++++++++ 3 files changed, 198 insertions(+) create mode 100644 lib/widget/category_list.dart create mode 100644 lib/widget/location_list.dart diff --git a/lib/widget/category_list.dart b/lib/widget/category_list.dart new file mode 100644 index 00000000..e307da4c --- /dev/null +++ b/lib/widget/category_list.dart @@ -0,0 +1,85 @@ + + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:inventree/inventree/model.dart'; +import 'package:inventree/inventree/part.dart'; +import 'package:inventree/widget/category_display.dart'; +import 'package:inventree/widget/paginator.dart'; +import 'package:inventree/widget/part_list.dart'; + +import 'package:inventree/widget/refreshable_state.dart'; +import "package:inventree/l10.dart"; + +class PartCategoryList extends StatefulWidget { + + const PartCategoryList(this.filters); + + final Map filters; + + @override + _PartCategoryListState createState() => _PartCategoryListState(filters); + +} + + +class _PartCategoryListState extends RefreshableState { + + _PartCategoryListState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().partCategories; + + @override + Widget getBody(BuildContext context) { + return PaginatedPartCategoryList(filters); + } +} + + +class PaginatedPartCategoryList extends StatefulWidget { + + const PaginatedPartCategoryList(this.filters); + + final Map filters; + + @override + _PaginatedPartCategoryListState createState() => _PaginatedPartCategoryListState(filters); +} + + +class _PaginatedPartCategoryListState extends PaginatedSearchState { + + _PaginatedPartCategoryListState(Map filters) : super(filters); + + @override + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreePartCategory().listPaginated(limit, offset, filters: params); + + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreePartCategory category = model as InvenTreePartCategory; + + return ListTile( + title: Text(category.name), + subtitle: Text(category.pathstring), + trailing: Text("${category.partcount}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CategoryDisplayWidget(category) + ) + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/widget/location_list.dart b/lib/widget/location_list.dart new file mode 100644 index 00000000..4bb25ac4 --- /dev/null +++ b/lib/widget/location_list.dart @@ -0,0 +1,87 @@ + + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:inventree/inventree/model.dart'; +import 'package:inventree/inventree/part.dart'; +import 'package:inventree/inventree/stock.dart'; +import 'package:inventree/widget/category_display.dart'; +import 'package:inventree/widget/location_display.dart'; +import 'package:inventree/widget/paginator.dart'; +import 'package:inventree/widget/part_list.dart'; + +import 'package:inventree/widget/refreshable_state.dart'; +import "package:inventree/l10.dart"; + + +class StockLocationList extends StatefulWidget { + + const StockLocationList(this.filters); + + final Map filters; + + @override + _StockLocationListState createState() => _StockLocationListState(filters); +} + + +class _StockLocationListState extends RefreshableState { + + _StockLocationListState(this.filters); + + final Map filters; + + @override + String getAppBarTitle(BuildContext context) => L10().stockLocations; + + @override + Widget getBody(BuildContext context) { + return PaginatedStockLocationList(filters); + } +} + + +class PaginatedStockLocationList extends StatefulWidget { + + const PaginatedStockLocationList(this.filters); + + final Map filters; + + @override + _PaginatedStockLocationListState createState() => _PaginatedStockLocationListState(filters); +} + + +class _PaginatedStockLocationListState extends PaginatedSearchState { + + _PaginatedStockLocationListState(Map filters) : super(filters); + + @override + Future requestPage(int limit, int offset, Map params) async { + + final page = await InvenTreeStockLocation().listPaginated(limit, offset, filters: params); + + return page; + } + + @override + Widget buildItem(BuildContext context, InvenTreeModel model) { + + InvenTreeStockLocation location = model as InvenTreeStockLocation; + + return ListTile( + title: Text(location.name), + subtitle: Text(location.pathstring), + trailing: Text("${location.itemcount}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LocationDisplayWidget(location) + ) + ); + }, + ); + } +} \ No newline at end of file diff --git a/lib/widget/search.dart b/lib/widget/search.dart index b1e93b47..7134a3a3 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -17,7 +17,9 @@ import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; import 'package:inventree/widget/stock_list.dart'; +import 'category_list.dart'; import 'company_list.dart'; +import 'location_list.dart'; // Widget for performing database-wide search @@ -207,6 +209,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().partCategories), leading: FaIcon(FontAwesomeIcons.sitemap), trailing: Text("${nCategoryResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PartCategoryList( + { + "original_search": query + } + ) + ) + ); + }, ) ); } @@ -241,6 +255,18 @@ class _SearchDisplayState extends RefreshableState { title: Text(L10().stockLocations), leading: FaIcon(FontAwesomeIcons.mapMarkerAlt), trailing: Text("${nLocationResults}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StockLocationList( + { + "original_search": query + } + ) + ) + ); + }, ) ); } From a5b52ea1ccaf5ff78b61ae8c4bae893d70b6dbe4 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 08:31:39 +1100 Subject: [PATCH 68/70] Linting --- lib/inventree/company.dart | 1 - lib/widget/category_display.dart | 26 ++++++++++---------------- lib/widget/category_list.dart | 18 +++++++----------- lib/widget/company_list.dart | 6 +----- lib/widget/home.dart | 2 +- lib/widget/location_display.dart | 27 ++++++++++++--------------- lib/widget/location_list.dart | 19 +++++++------------ lib/widget/paginator.dart | 8 +++++--- lib/widget/part_detail.dart | 7 +++---- lib/widget/part_list.dart | 6 ++---- lib/widget/purchase_order_detail.dart | 6 +++--- lib/widget/purchase_order_list.dart | 4 +--- lib/widget/search.dart | 25 +++++++++++-------------- lib/widget/stock_list.dart | 7 ++----- 14 files changed, 65 insertions(+), 97 deletions(-) diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 59246769..2dec8922 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -1,5 +1,4 @@ import "dart:async"; -import "dart:io"; import "package:inventree/api.dart"; import "package:inventree/inventree/model.dart"; diff --git a/lib/widget/category_display.dart b/lib/widget/category_display.dart index 86856156..9e3e7e21 100644 --- a/lib/widget/category_display.dart +++ b/lib/widget/category_display.dart @@ -1,24 +1,18 @@ - -import "package:inventree/api.dart"; -import "package:inventree/app_colors.dart"; -import "package:inventree/app_settings.dart"; -import "package:inventree/inventree/part.dart"; -import "package:inventree/inventree/sentry.dart"; -import 'package:inventree/widget/part_list.dart'; -import "package:inventree/widget/progress.dart"; - -import "package:inventree/l10.dart"; - -import "package:inventree/widget/part_detail.dart"; -import "package:inventree/widget/refreshable_state.dart"; -import "package:inventree/widget/paginator.dart"; - import "package:flutter/cupertino.dart"; import "package:flutter/foundation.dart"; import "package:flutter/material.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; + +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/part_list.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/part_detail.dart"; +import "package:inventree/widget/refreshable_state.dart"; + class CategoryDisplayWidget extends StatefulWidget { diff --git a/lib/widget/category_list.dart b/lib/widget/category_list.dart index e307da4c..33673d6c 100644 --- a/lib/widget/category_list.dart +++ b/lib/widget/category_list.dart @@ -1,15 +1,11 @@ +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; - -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/widget/category_display.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/part_list.dart'; - -import 'package:inventree/widget/refreshable_state.dart'; +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/part.dart"; +import "package:inventree/widget/category_display.dart"; +import "package:inventree/widget/paginator.dart"; +import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; class PartCategoryList extends StatefulWidget { diff --git a/lib/widget/company_list.dart b/lib/widget/company_list.dart index 6d2a3f4e..c2bb5996 100644 --- a/lib/widget/company_list.dart +++ b/lib/widget/company_list.dart @@ -1,18 +1,14 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/api.dart"; import "package:inventree/inventree/company.dart"; -import 'package:inventree/inventree/model.dart'; -import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/model.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/company_detail.dart"; -import "package:inventree/l10.dart"; - class CompanyListWidget extends StatefulWidget { diff --git a/lib/widget/home.dart b/lib/widget/home.dart index c58954e7..1a85c711 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -17,7 +17,7 @@ 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/purchase_order_list.dart"; -import 'package:inventree/widget/search.dart'; +import "package:inventree/widget/search.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/drawer.dart"; diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 17ace857..138d3f04 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -1,22 +1,19 @@ -import "package:inventree/api.dart"; -import "package:inventree/app_colors.dart"; -import "package:inventree/app_settings.dart"; -import "package:inventree/barcode.dart"; -import "package:inventree/inventree/sentry.dart"; -import "package:inventree/inventree/stock.dart"; -import "package:inventree/widget/progress.dart"; - -import "package:inventree/widget/refreshable_state.dart"; -import "package:inventree/widget/stock_detail.dart"; -import "package:inventree/widget/paginator.dart"; -import "package:inventree/l10.dart"; - import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; import "package:flutter/foundation.dart"; + import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; -import 'package:inventree/widget/stock_list.dart'; + +import "package:inventree/api.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/barcode.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/progress.dart"; +import "package:inventree/widget/refreshable_state.dart"; +import "package:inventree/widget/stock_detail.dart"; +import "package:inventree/l10.dart"; +import "package:inventree/widget/stock_list.dart"; + class LocationDisplayWidget extends StatefulWidget { diff --git a/lib/widget/location_list.dart b/lib/widget/location_list.dart index 4bb25ac4..b766ca08 100644 --- a/lib/widget/location_list.dart +++ b/lib/widget/location_list.dart @@ -1,17 +1,12 @@ +import "package:flutter/cupertino.dart"; +import "package:flutter/material.dart"; +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/stock.dart"; +import "package:inventree/widget/location_display.dart"; +import "package:inventree/widget/paginator.dart"; -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/inventree/part.dart'; -import 'package:inventree/inventree/stock.dart'; -import 'package:inventree/widget/category_display.dart'; -import 'package:inventree/widget/location_display.dart'; -import 'package:inventree/widget/paginator.dart'; -import 'package:inventree/widget/part_list.dart'; - -import 'package:inventree/widget/refreshable_state.dart'; +import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index d46ec19d..9c6cfdec 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -1,8 +1,10 @@ import "package:flutter/material.dart"; + import "package:font_awesome_flutter/font_awesome_flutter.dart"; -import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import 'package:inventree/inventree/model.dart'; -import 'package:inventree/inventree/sentry.dart'; +import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; + +import "package:inventree/inventree/model.dart"; +import "package:inventree/inventree/sentry.dart"; import "package:inventree/l10.dart"; diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 254e1a53..c31ecffe 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -2,10 +2,11 @@ import "package:flutter/cupertino.dart"; import "package:flutter/foundation.dart"; import "package:flutter/material.dart"; + import "package:font_awesome_flutter/font_awesome_flutter.dart"; + import "package:inventree/app_colors.dart"; import "package:inventree/inventree/stock.dart"; - import "package:inventree/l10.dart"; import "package:inventree/widget/part_attachments_widget.dart"; import "package:inventree/widget/part_notes.dart"; @@ -16,9 +17,7 @@ import "package:inventree/api.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/part_image_widget.dart"; import "package:inventree/widget/stock_detail.dart"; - -import "package:inventree/widget/location_display.dart"; -import 'package:inventree/widget/stock_list.dart'; +import "package:inventree/widget/stock_list.dart"; class PartDetailWidget extends StatefulWidget { diff --git a/lib/widget/part_list.dart b/lib/widget/part_list.dart index fc4635f9..a6fb6fef 100644 --- a/lib/widget/part_list.dart +++ b/lib/widget/part_list.dart @@ -1,12 +1,10 @@ import "package:flutter/material.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; -import 'package:inventree/inventree/model.dart'; + +import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/part.dart"; -import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/part_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; - import "package:inventree/api.dart"; import "package:inventree/app_settings.dart"; import "package:inventree/l10.dart"; diff --git a/lib/widget/purchase_order_detail.dart b/lib/widget/purchase_order_detail.dart index 4275bb89..1558ddbc 100644 --- a/lib/widget/purchase_order_detail.dart +++ b/lib/widget/purchase_order_detail.dart @@ -1,6 +1,8 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; + import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:one_context/one_context.dart"; import "package:inventree/api.dart"; import "package:inventree/api_form.dart"; @@ -11,10 +13,8 @@ import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/widget/company_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; -import "package:inventree/widget/location_display.dart"; import "package:inventree/widget/snacks.dart"; -import 'package:inventree/widget/stock_list.dart'; -import "package:one_context/one_context.dart"; +import "package:inventree/widget/stock_list.dart"; class PurchaseOrderDetailWidget extends StatefulWidget { diff --git a/lib/widget/purchase_order_list.dart b/lib/widget/purchase_order_list.dart index 0c284cfc..d1a107c9 100644 --- a/lib/widget/purchase_order_list.dart +++ b/lib/widget/purchase_order_list.dart @@ -1,10 +1,8 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; import "package:inventree/inventree/company.dart"; -import 'package:inventree/inventree/model.dart'; -import "package:inventree/inventree/sentry.dart"; +import "package:inventree/inventree/model.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/purchase_order_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; diff --git a/lib/widget/search.dart b/lib/widget/search.dart index 7134a3a3..387eb8f3 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -1,25 +1,22 @@ import "dart:async"; -import 'package:inventree/inventree/company.dart'; -import 'package:inventree/inventree/purchase_order.dart'; -import "package:inventree/widget/part_detail.dart"; -import 'package:inventree/widget/part_list.dart'; -import "package:inventree/widget/progress.dart"; -import 'package:inventree/widget/purchase_order_list.dart'; -import 'package:inventree/widget/refreshable_state.dart'; -import "package:inventree/widget/snacks.dart"; -import "package:inventree/widget/stock_detail.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; + import "package:font_awesome_flutter/font_awesome_flutter.dart"; + +import "package:inventree/inventree/company.dart"; +import "package:inventree/inventree/purchase_order.dart"; +import "package:inventree/widget/part_list.dart"; +import "package:inventree/widget/purchase_order_list.dart"; +import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/stock.dart"; -import 'package:inventree/widget/stock_list.dart'; - -import 'category_list.dart'; -import 'company_list.dart'; -import 'location_list.dart'; +import "package:inventree/widget/stock_list.dart"; +import "package:inventree/widget/category_list.dart"; +import "package:inventree/widget/company_list.dart"; +import "package:inventree/widget/location_list.dart"; // Widget for performing database-wide search diff --git a/lib/widget/stock_list.dart b/lib/widget/stock_list.dart index 96040860..9364f23f 100644 --- a/lib/widget/stock_list.dart +++ b/lib/widget/stock_list.dart @@ -1,17 +1,14 @@ import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -import "package:infinite_scroll_pagination/infinite_scroll_pagination.dart"; -import 'package:inventree/inventree/model.dart'; -import "package:inventree/inventree/sentry.dart"; + +import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; - import "package:inventree/l10.dart"; import "package:inventree/app_settings.dart"; import "package:inventree/widget/stock_detail.dart"; - import "package:inventree/api.dart"; class StockItemList extends StatefulWidget { From 4a238d0530a40fe0a6523dbffdf737b2af3a56f8 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 4 Oct 2021 08:37:04 +1100 Subject: [PATCH 69/70] Home page improvements - Add button for "starred" parts - Remove ganky line on buttons --- lib/widget/home.dart | 52 ++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/lib/widget/home.dart b/lib/widget/home.dart index 1a85c711..da0fabb7 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -1,26 +1,25 @@ -import "package:inventree/app_colors.dart"; -import "package:inventree/settings/settings.dart"; -import "package:inventree/user_profile.dart"; import "package:flutter/cupertino.dart"; import "package:flutter/material.dart"; -import "package:inventree/l10.dart"; - import "package:font_awesome_flutter/font_awesome_flutter.dart"; +import "package:inventree/app_colors.dart"; +import "package:inventree/settings/settings.dart"; +import "package:inventree/user_profile.dart"; +import "package:inventree/l10.dart"; import "package:inventree/barcode.dart"; import "package:inventree/api.dart"; - import "package:inventree/settings/login.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/part_list.dart"; import "package:inventree/widget/purchase_order_list.dart"; import "package:inventree/widget/search.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/drawer.dart"; + class InvenTreeHomePage extends StatefulWidget { const InvenTreeHomePage({Key? key}) : super(key: key); @@ -70,14 +69,18 @@ class _InvenTreeHomePageState extends State { Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget())); } - /* void _showStarredParts(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; - // TODO - // Navigator.push(context, MaterialPageRoute(builder: (context) => StarredPartWidget())); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PartList({ + "starred": "true" + }) + ) + ); } - */ void _showStock(BuildContext context) { if (!InvenTreeAPI().checkConnection(context)) return; @@ -167,8 +170,9 @@ class _InvenTreeHomePageState extends State { color: connected && allowed ? COLOR_CLICK : Colors.grey, ), Divider( - height: 10, + height: 12, thickness: 0, + color: Colors.transparent, ), Text( label, @@ -222,22 +226,14 @@ class _InvenTreeHomePageState extends State { _showParts(context); } ), - - // TODO - Re-add starred parts link - /* - Column( - children: [ - IconButton( - icon: FaIcon(FontAwesomeIcons.solidStar), - onPressed: () { - - }, - ), - Text("Starred Parts"), - ] - ), - */ - + _iconButton( + context, + L10().partsStarred, + FontAwesomeIcons.solidStar, + callback: () { + _showStarredParts(context); + } + ), _iconButton( context, L10().stock, From 21271d71c1148a69bc8019598bd03d3a102299ec Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 5 Oct 2021 21:19:35 +1100 Subject: [PATCH 70/70] Better handling of date inputs within forms - use datetime_picker_formfield - Allow null / empty dates --- lib/api_form.dart | 54 ++++++++++++++++++++++++++++---------------- lib/widget/home.dart | 2 +- pubspec.lock | 6 ++--- pubspec.yaml | 2 +- 4 files changed, 39 insertions(+), 25 deletions(-) diff --git a/lib/api_form.dart b/lib/api_form.dart index 87acf24c..c2814d99 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -1,9 +1,10 @@ import "dart:ui"; import "dart:io"; +import "package:intl/intl.dart"; import "package:font_awesome_flutter/font_awesome_flutter.dart"; import "package:dropdown_search/dropdown_search.dart"; -import "package:date_field/date_field.dart"; +import "package:datetime_picker_formfield/datetime_picker_formfield.dart"; import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; @@ -21,7 +22,6 @@ import "package:flutter/material.dart"; import "package:inventree/widget/snacks.dart"; - /* * Class that represents a single "form field", * defined by the InvenTree API @@ -114,11 +114,11 @@ class APIFormField { dynamic get value => data["value"] ?? data["instance_value"] ?? defaultValue; // Render value to string (for form submission) - String renderToString() { - if (value == null) { + String renderValueToString() { + if (data["value"] == null) { return ""; } else { - return value.toString(); + return data["value"].toString(); } } @@ -359,22 +359,37 @@ class APIFormField { // Field for displaying and selecting dates Widget _constructDateField() { - return DateTimeFormField( - mode: DateTimeFieldPickerMode.date, + DateTime? currentDate = DateTime.tryParse((value ?? "")as String); + + return InputDecorator( decoration: InputDecoration( - helperText: helpText, - helperStyle: _helperStyle(), labelText: label, labelStyle: _labelStyle(), + helperStyle: _helperStyle(), + helperText: helpText, ), - initialValue: DateTime.tryParse((value ?? "") as String), - autovalidateMode: AutovalidateMode.disabled, - validator: (e) { - // TODO - }, - onDateSelected: (DateTime dt) { - data["value"] = dt.toString().split(" ").first; - }, + child: DateTimeField( + format: DateFormat("yyyy-MM-dd"), + initialValue: currentDate, + onChanged: (DateTime? time) { + // Save the time string + if (time == null) { + data["value"] = null; + } else { + data["value"] = time.toString().split(" ").first; + } + }, + onShowPicker: (context, value) async { + final time = await showDatePicker( + context: context, + initialDate: currentDate ?? DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime(2100), + ); + + return time; + }, + ) ); } @@ -403,7 +418,6 @@ class APIFormField { FilePickerDialog.pickFile( message: L10().attachmentSelect, onPicked: (file) { - // print("${file.path}"); // Display the filename controller.text = file.path.split("/").last; @@ -1122,7 +1136,7 @@ class _APIFormWidgetState extends State { if (field.isSimple) { // Simple top-level field data - data[field.name] = field.renderToString(); + data[field.name] = field.data["value"]; } else { // Not so simple... (WHY DID I MAKE THE API SO COMPLEX?) if (field.parent.isNotEmpty) { @@ -1136,7 +1150,7 @@ class _APIFormWidgetState extends State { parent = parent.first; } - parent[field.name] = field.renderToString(); + parent[field.name] = field.data["value"]; // Nested fields must be handled as an array! // For now, we only allow single length nested fields diff --git a/lib/widget/home.dart b/lib/widget/home.dart index da0fabb7..c72ebcae 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -94,7 +94,7 @@ class _InvenTreeHomePageState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget() + builder: (context) => PurchaseOrderListWidget(filters: {}) ) ); } diff --git a/pubspec.lock b/pubspec.lock index 9d6d2aca..6a199be7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,13 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.3" - date_field: + datetime_picker_formfield: dependency: "direct main" description: - name: date_field + name: datetime_picker_formfield url: "https://pub.dartlang.org" source: hosted - version: "2.1.2" + version: "2.0.0" device_info_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 5b9a46bb..a6b94af1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: cached_network_image: ^3.1.0 # Download and cache remote images camera: # Camera cupertino_icons: ^1.0.3 - date_field: ^2.1.2 # Date / time picker + datetime_picker_formfield: ^2.0.0 # Date / time picker device_info_plus: ^2.1.0 # Information about the device dropdown_search: 0.6.3 # Dropdown autocomplete form fields file_picker: ^4.0.0 # Select files from the device