diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 516672f4..022d0642 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -323,6 +323,15 @@ class InvenTreeStockItem extends InvenTreeModel { double get quantity => double.tryParse(jsondata['quantity'].toString() ?? '0'); + String get quantityString { + + if (quantity.toInt() == quantity) { + return quantity.toInt().toString(); + } else { + return quantity.toString(); + } + } + int get locationId => jsondata['location'] as int ?? -1; bool isSerialized() => serialNumber != null && quantity.toInt() == 1; diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index 803476ab..3baa1c44 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -161,7 +161,39 @@ void hideProgressDialog(BuildContext context) { Navigator.pop(context); } -void showFormDialog(BuildContext context, String title, {GlobalKey key, List fields, List actions}) { +void showFormDialog(BuildContext context, String title, {GlobalKey key, List fields, List actions, Function callback}) { + + // Undefined actions = OK + Cancel + if (actions == null) { + actions = [ + FlatButton( + child: Text(I18N.of(context).cancel), + onPressed: () { + // Close the form + Navigator.pop(context); + } + ), + FlatButton( + child: Text(I18N.of(context).save), + onPressed: () { + if (key.currentState.validate()) { + key.currentState.save(); + + // Close the dialog + Navigator.pop(context); + + // Callback + if (callback != null) { + callback(); + } + + + } + } + ) + ]; + } + showDialog( context: context, builder: (BuildContext context) { diff --git a/lib/widget/location_display.dart b/lib/widget/location_display.dart index 5498793a..8d6b7b7c 100644 --- a/lib/widget/location_display.dart +++ b/lib/widget/location_display.dart @@ -1,13 +1,18 @@ import 'package:InvenTree/api.dart'; import 'package:InvenTree/inventree/stock.dart'; import 'package:InvenTree/preferences.dart'; + +import 'package:InvenTree/widget/refreshable_state.dart'; +import 'package:InvenTree/widget/fields.dart'; +import 'package:InvenTree/widget/dialogs.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:flutter/foundation.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:InvenTree/widget/refreshable_state.dart'; class LocationDisplayWidget extends StatefulWidget { @@ -25,6 +30,8 @@ class _LocationDisplayState extends RefreshableState { final InvenTreeStockLocation location; + final _editLocationKey = GlobalKey(); + @override String getAppBarTitle(BuildContext context) { return "Stock Location"; } @@ -33,13 +40,53 @@ class _LocationDisplayState extends RefreshableState { return [ IconButton( icon: FaIcon(FontAwesomeIcons.edit), - tooltip: "Edit", - // TODO - Edit stock location - onPressed: null, + tooltip: I18N.of(context).edit, + onPressed: _editLocationDialog, ) ]; } + void _editLocation(Map values) async { + + final bool result = await location.update(context, values: values); + + showSnackIcon( + refreshableKey, + result ? "Location edited" : "Location editing failed", + success: result + ); + + refresh(); + } + + void _editLocationDialog() { + // Values which an be edited + var _name; + var _description; + + showFormDialog(context, I18N.of(context).editLocation, + key: _editLocationKey, + callback: () { + _editLocation({ + "name": _name, + "description": _description + }); + }, + fields: [ + StringField( + label: I18N.of(context).name, + initial: location.name, + onSaved: (value) => _name = value, + ), + StringField( + label: I18N.of(context).description, + initial: location.description, + onSaved: (value) => _description = value, + ) + ] + ); + } + _LocationDisplayState(this.location) {} List _sublocations = List(); @@ -67,6 +114,9 @@ class _LocationDisplayState extends RefreshableState { int pk = location?.pk ?? -1; + // Reload location information + await location.reload(context); + // Request a list of sub-locations under this one await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) { _sublocations.clear(); diff --git a/lib/widget/part_detail.dart b/lib/widget/part_detail.dart index 64cedbe9..6c34fc53 100644 --- a/lib/widget/part_detail.dart +++ b/lib/widget/part_detail.dart @@ -1,4 +1,6 @@ +import 'dart:io'; +import 'package:InvenTree/widget/snacks.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -74,9 +76,13 @@ class _PartDisplayState extends RefreshableState { void _savePart(Map values) async { - Navigator.of(context).pop(); + final bool result = await part.update(context, values: values); - var response = await part.update(context, values: values); + showSnackIcon( + refreshableKey, + result ? "Part edited" : "Part editing failed", + success: result + ); refresh(); } @@ -96,34 +102,18 @@ class _PartDisplayState extends RefreshableState { var _name; var _description; var _ipn; - var _revision; var _keywords; showFormDialog(context, I18N.of(context).editPart, key: _editPartKey, - actions: [ - FlatButton( - child: Text(I18N.of(context).cancel), - onPressed: () { - Navigator.pop(context); - }, - ), - FlatButton( - child: Text(I18N.of(context).save), - onPressed: () { - if (_editPartKey.currentState.validate()) { - _editPartKey.currentState.save(); - - _savePart({ - "name": _name, - "description": _description, - "IPN": _ipn, - "keywords": _keywords, - }); - } - }, - ), - ], + callback: () { + _savePart({ + "name": _name, + "description": _description, + "IPN": _ipn, + "keywords": _keywords + }); + }, fields: [ StringField( label: I18N.of(context).name, @@ -163,7 +153,7 @@ class _PartDisplayState extends RefreshableState { onTap: () { Navigator.push( context, - MaterialPageRoute(builder: (context) => FullScreenWidget(part.name, part.image)) + MaterialPageRoute(builder: (context) => FullScreenWidget(part.fullname, part.image)) ); }), ) diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index 7bf1bbde..53ed0a8e 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -8,6 +8,8 @@ import 'package:InvenTree/widget/drawer.dart'; abstract class RefreshableState extends State { + final refreshableKey = GlobalKey(); + // Storage for context once "Build" is called BuildContext context; @@ -80,6 +82,7 @@ abstract class RefreshableState extends State { this.context = context; return Scaffold( + key: refreshableKey, appBar: getAppBar(context), drawer: getDrawer(context), floatingActionButton: getFab(context), diff --git a/lib/widget/snacks.dart b/lib/widget/snacks.dart new file mode 100644 index 00000000..9b2f3cad --- /dev/null +++ b/lib/widget/snacks.dart @@ -0,0 +1,33 @@ + +/* + * Display a snackbar with: + * + * a) Text on the left + * b) Icon on the right + * + * | Text | + */ + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; + +void showSnackIcon(GlobalKey key, String text, {IconData icon, bool success}) { + + // If icon not specified, use the success status + if (icon == null) { + icon = (success == true) ? FontAwesomeIcons.checkCircle : FontAwesomeIcons.timesCircle; + } + + key.currentState.showSnackBar( + SnackBar( + content: Row( + children: [ + Text(text), + Spacer(), + FaIcon(icon) + ] + ), + ) + ); +} \ No newline at end of file diff --git a/lib/widget/stock_detail.dart b/lib/widget/stock_detail.dart index dc4beddd..2acca4c7 100644 --- a/lib/widget/stock_detail.dart +++ b/lib/widget/stock_detail.dart @@ -1,4 +1,5 @@ +import 'dart:io'; import 'package:InvenTree/barcode.dart'; import 'package:InvenTree/inventree/stock.dart'; @@ -8,6 +9,7 @@ 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'; +import 'package:InvenTree/widget/snacks.dart'; import 'package:InvenTree/widget/stock_item_test_results.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -22,6 +24,7 @@ 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'; +import 'package:http/http.dart'; class StockDetailWidget extends StatefulWidget { @@ -71,10 +74,10 @@ class _StockItemDisplayState extends RefreshableState { var response = await item.addStock(quantity, notes: _notesController.text); _notesController.clear(); + _stockUpdateMessage(response); + // TODO - Handle error cases refresh(); - - // TODO - Display a snackbar here indicating the action was successful (or otherwise) } void _addStockDialog() async {