mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +00:00
Add snackbar with icon
- stock adjust - part edit - location edit
This commit is contained in:
parent
ce2a866384
commit
c8c056f96d
@ -323,6 +323,15 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
double get quantity => double.tryParse(jsondata['quantity'].toString() ?? '0');
|
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;
|
int get locationId => jsondata['location'] as int ?? -1;
|
||||||
|
|
||||||
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
|
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
|
||||||
|
@ -161,7 +161,39 @@ void hideProgressDialog(BuildContext context) {
|
|||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void showFormDialog(BuildContext context, String title, {GlobalKey<FormState> key, List<Widget> fields, List<Widget> actions}) {
|
void showFormDialog(BuildContext context, String title, {GlobalKey<FormState> key, List<Widget> fields, List<Widget> actions, Function callback}) {
|
||||||
|
|
||||||
|
// Undefined actions = OK + Cancel
|
||||||
|
if (actions == null) {
|
||||||
|
actions = <Widget>[
|
||||||
|
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(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
import 'package:InvenTree/inventree/stock.dart';
|
import 'package:InvenTree/inventree/stock.dart';
|
||||||
import 'package:InvenTree/preferences.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:InvenTree/widget/stock_detail.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:InvenTree/widget/refreshable_state.dart';
|
|
||||||
|
|
||||||
class LocationDisplayWidget extends StatefulWidget {
|
class LocationDisplayWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -25,6 +30,8 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
final InvenTreeStockLocation location;
|
final InvenTreeStockLocation location;
|
||||||
|
|
||||||
|
final _editLocationKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
|
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
|
||||||
|
|
||||||
@ -33,13 +40,53 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
return <Widget>[
|
return <Widget>[
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.edit),
|
icon: FaIcon(FontAwesomeIcons.edit),
|
||||||
tooltip: "Edit",
|
tooltip: I18N.of(context).edit,
|
||||||
// TODO - Edit stock location
|
onPressed: _editLocationDialog,
|
||||||
onPressed: null,
|
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _editLocation(Map<String, String> 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: <Widget> [
|
||||||
|
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) {}
|
_LocationDisplayState(this.location) {}
|
||||||
|
|
||||||
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
||||||
@ -67,6 +114,9 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
int pk = location?.pk ?? -1;
|
int pk = location?.pk ?? -1;
|
||||||
|
|
||||||
|
// Reload location information
|
||||||
|
await location.reload(context);
|
||||||
|
|
||||||
// Request a list of sub-locations under this one
|
// Request a list of sub-locations under this one
|
||||||
await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
|
await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
|
||||||
_sublocations.clear();
|
_sublocations.clear();
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:InvenTree/widget/snacks.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -74,9 +76,13 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
|
|
||||||
void _savePart(Map<String, String> values) async {
|
void _savePart(Map<String, String> 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();
|
refresh();
|
||||||
}
|
}
|
||||||
@ -96,34 +102,18 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
var _name;
|
var _name;
|
||||||
var _description;
|
var _description;
|
||||||
var _ipn;
|
var _ipn;
|
||||||
var _revision;
|
|
||||||
var _keywords;
|
var _keywords;
|
||||||
|
|
||||||
showFormDialog(context, I18N.of(context).editPart,
|
showFormDialog(context, I18N.of(context).editPart,
|
||||||
key: _editPartKey,
|
key: _editPartKey,
|
||||||
actions: <Widget>[
|
callback: () {
|
||||||
FlatButton(
|
_savePart({
|
||||||
child: Text(I18N.of(context).cancel),
|
"name": _name,
|
||||||
onPressed: () {
|
"description": _description,
|
||||||
Navigator.pop(context);
|
"IPN": _ipn,
|
||||||
},
|
"keywords": _keywords
|
||||||
),
|
});
|
||||||
FlatButton(
|
},
|
||||||
child: Text(I18N.of(context).save),
|
|
||||||
onPressed: () {
|
|
||||||
if (_editPartKey.currentState.validate()) {
|
|
||||||
_editPartKey.currentState.save();
|
|
||||||
|
|
||||||
_savePart({
|
|
||||||
"name": _name,
|
|
||||||
"description": _description,
|
|
||||||
"IPN": _ipn,
|
|
||||||
"keywords": _keywords,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
fields: <Widget>[
|
fields: <Widget>[
|
||||||
StringField(
|
StringField(
|
||||||
label: I18N.of(context).name,
|
label: I18N.of(context).name,
|
||||||
@ -163,7 +153,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(builder: (context) => FullScreenWidget(part.name, part.image))
|
MaterialPageRoute(builder: (context) => FullScreenWidget(part.fullname, part.image))
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -8,6 +8,8 @@ import 'package:InvenTree/widget/drawer.dart';
|
|||||||
|
|
||||||
abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
||||||
|
|
||||||
|
final refreshableKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
// Storage for context once "Build" is called
|
// Storage for context once "Build" is called
|
||||||
BuildContext context;
|
BuildContext context;
|
||||||
|
|
||||||
@ -80,6 +82,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
|||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
key: refreshableKey,
|
||||||
appBar: getAppBar(context),
|
appBar: getAppBar(context),
|
||||||
drawer: getDrawer(context),
|
drawer: getDrawer(context),
|
||||||
floatingActionButton: getFab(context),
|
floatingActionButton: getFab(context),
|
||||||
|
33
lib/widget/snacks.dart
Normal file
33
lib/widget/snacks.dart
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Display a snackbar with:
|
||||||
|
*
|
||||||
|
* a) Text on the left
|
||||||
|
* b) Icon on the right
|
||||||
|
*
|
||||||
|
* | Text <icon> |
|
||||||
|
*/
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
void showSnackIcon(GlobalKey<ScaffoldState> 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)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
|
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:InvenTree/barcode.dart';
|
import 'package:InvenTree/barcode.dart';
|
||||||
import 'package:InvenTree/inventree/stock.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/location_display.dart';
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_detail.dart';
|
||||||
import 'package:InvenTree/widget/refreshable_state.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_item_test_results.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -22,6 +24,7 @@ import 'package:flutter_typeahead/flutter_typeahead.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
|
||||||
class StockDetailWidget extends StatefulWidget {
|
class StockDetailWidget extends StatefulWidget {
|
||||||
|
|
||||||
@ -71,10 +74,10 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
var response = await item.addStock(quantity, notes: _notesController.text);
|
var response = await item.addStock(quantity, notes: _notesController.text);
|
||||||
_notesController.clear();
|
_notesController.clear();
|
||||||
|
|
||||||
|
_stockUpdateMessage(response);
|
||||||
|
|
||||||
// TODO - Handle error cases
|
// TODO - Handle error cases
|
||||||
refresh();
|
refresh();
|
||||||
|
|
||||||
// TODO - Display a snackbar here indicating the action was successful (or otherwise)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addStockDialog() async {
|
void _addStockDialog() async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user