diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index ebcef711..5547266c 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -142,6 +142,16 @@ class InvenTreeStockItem extends InvenTreeModel { return loc; } + String get locationPathString { + String path = ''; + + if (jsondata.containsKey('location_detail')) { + path = jsondata['location_detail']['pathstring'] ?? ''; + } + + return path; + } + String get displayQuantity { // Display either quantity or serial number! diff --git a/lib/widget/fields.dart b/lib/widget/fields.dart new file mode 100644 index 00000000..c401a7b8 --- /dev/null +++ b/lib/widget/fields.dart @@ -0,0 +1,26 @@ + +import 'package:flutter/material.dart'; + +class QuantityField extends TextFormField { + + QuantityField({String label = "", String hint = "", double max = null, TextEditingController controller}) : + super( + decoration: InputDecoration( + labelText: label, + hintText: hint, + ), + controller: controller, + keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + validator: (value) { + if (value.isEmpty) return "Quantity is empty"; + + double quantity = double.tryParse(value); + + if (quantity == null) return "Invalid quantity"; + if (quantity <= 0) return "Quantity must be positive"; + if ((max != null) && (quantity > max)) return "Quantity must not exceed ${max}"; + + return null; + }, + ); +} \ No newline at end of file diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 4382e404..2c5691f2 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -129,8 +129,8 @@ class _LocationDisplayState extends RefreshableState { return ListView( children: [ - locationDescriptionCard(), - ExpansionPanelList( + locationDescriptionCard(), + ExpansionPanelList( expansionCallback: (int index, bool isExpanded) { setState(() { switch (index) { diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 39216527..94b6d0a9 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -204,6 +204,10 @@ class _PartDisplayState extends RefreshableState { icon: FaIcon(FontAwesomeIcons.infoCircle), title: Text("Details"), ), + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.thList), + title: Text("BOM"), + ), BottomNavigationBarItem( icon: FaIcon(FontAwesomeIcons.boxes), title: Text("Stock"), diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index bb1b7aca..a85fbc3b 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -54,6 +54,10 @@ abstract class RefreshableState extends State { return null; } + Widget getFab(BuildContext context) { + return null; + } + @override Widget build(BuildContext context) { @@ -63,6 +67,7 @@ abstract class RefreshableState extends State { return Scaffold( appBar: getAppBar(context), drawer: getDrawer(context), + floatingActionButton: getFab(context), body: RefreshIndicator( onRefresh: refresh, child: getBody(context) diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index e77e6cc8..36f56a04 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -2,6 +2,7 @@ import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/inventree/part.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/refreshable_state.dart'; @@ -12,7 +13,7 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/widget/drawer.dart'; import 'package:InvenTree/widget/refreshable_state.dart'; - +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:flutter/services.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; @@ -98,6 +99,9 @@ class _StockItemDisplayState extends RefreshableState { } void _addStockDialog() async { + + _quantityController.clear(); + showDialog(context: context, builder: (BuildContext context) { return AlertDialog( @@ -117,22 +121,10 @@ class _StockItemDisplayState extends RefreshableState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - Text("Current Quantity: ${item.quantity}"), - TextFormField( - decoration: InputDecoration( - labelText: "Add stock", - ), - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), + Text("Current stock: ${item.quantity}"), + QuantityField( + label: "Add Stock", controller: _quantityController, - validator: (value) { - if (value.isEmpty) return "Value cannot be empty"; - - double quantity = double.tryParse(value); - if (quantity == null) return "Value cannot be converted to a number"; - if (quantity <= 0) return "Value must be positive"; - - return null; - }, ), TextFormField( decoration: InputDecoration( @@ -164,6 +156,9 @@ class _StockItemDisplayState extends RefreshableState { } void _removeStockDialog() { + + _quantityController.clear(); + showDialog(context: context, builder: (BuildContext context) { return AlertDialog( @@ -183,25 +178,11 @@ class _StockItemDisplayState extends RefreshableState { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Current quantity: ${item.quantity}"), - TextFormField( - decoration: InputDecoration( - labelText: "Remove stock", - ), + Text("Current stock: ${item.quantity}"), + QuantityField( + label: "Remove stock", controller: _quantityController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - validator: (value) { - if (value.isEmpty) return "Value cannot be empty"; - - double quantity = double.tryParse(value); - - if (quantity == null) return "Value cannot be converted to a number"; - if (quantity <= 0) return "Value must be positive"; - - if (quantity > item.quantity) return "Cannot take more than current quantity"; - - return null; - }, + max: item.quantity, ), TextFormField( decoration: InputDecoration( @@ -253,22 +234,10 @@ class _StockItemDisplayState extends RefreshableState { crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ - TextFormField( - decoration: InputDecoration( - labelText: "Count stock", - hintText: "${item.quantity}", - ), + QuantityField( + label: "Count Stock", + hint: "${item.quantity}", controller: _quantityController, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - validator: (value) { - if (value.isEmpty) return "Value cannot be empty"; - - double quantity = double.tryParse(value); - if (quantity == null) return "Value cannot be converted to a number"; - if (quantity < 0) return "Value cannot be negative"; - - return null; - }, ), TextFormField( decoration: InputDecoration( @@ -354,13 +323,12 @@ class _StockItemDisplayState extends RefreshableState { } - // Location information if (item.locationName.isNotEmpty) { tiles.add( ListTile( title: Text("Stock Location"), - subtitle: Text("${item.locationName}"), + subtitle: Text("${item.locationPathString}"), leading: FaIcon(FontAwesomeIcons.mapMarkerAlt), onTap: () { if (item.locationId > 0) { @@ -466,6 +434,24 @@ class _StockItemDisplayState extends RefreshableState { return buttons; } + @override + Widget getBottomNavBar(BuildContext context) { + return BottomNavigationBar( + currentIndex: 0, + onTap: null, + items: const [ + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.infoCircle), + title: Text("Details"), + ), + BottomNavigationBarItem( + icon: FaIcon(FontAwesomeIcons.history), + title: Text("History"), + ) + ] + ); + } + @override Widget getBody(BuildContext context) { return ListView( @@ -474,23 +460,12 @@ class _StockItemDisplayState extends RefreshableState { } @override - Widget build(BuildContext context) { - - this.context = context; - - return Scaffold( - appBar: getAppBar(context), - drawer: getDrawer(context), - floatingActionButton: SpeedDial( + Widget getFab(BuildContext context) { + return SpeedDial( visible: true, animatedIcon: AnimatedIcons.menu_close, heroTag: 'stock-item-fab', children: actionButtons(), - ), - body: RefreshIndicator( - onRefresh: refresh, - child: getBody(context) - ) - ); + ); } } \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index f6ef806d..5df67b51 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -76,6 +76,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.0" + flutter_keyboard_visibility: + dependency: transitive + description: + name: flutter_keyboard_visibility + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.0" flutter_launcher_icons: dependency: "direct dev" description: @@ -102,6 +109,13 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_typeahead: + dependency: "direct main" + description: + name: flutter_typeahead + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.0" flutter_web_plugins: dependency: transitive description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index f8b31f8f..b59d1b4c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: font_awesome_flutter: ^8.8.1 # FontAwesome icon set flutter_speed_dial: ^1.2.5 # FAB menu elements sentry: ^3.0.1 # Error reporting + flutter_typeahead: ^1.8.0 # Auto-complete input field dev_dependencies: flutter_test: