mirror of
https://github.com/inventree/inventree-app.git
synced 2025-06-15 19:55:27 +00:00
UX Overhaul (#300)
* Add "global actions" to title bar * Implement actions * Add "speed dial" action buttons * tweak global action icons * Refactor actions for "stock item" display * Refactor "part" detail * part category * SupplierPart * More updates * Add BottomAppBar * Add a global bottom app bar * Move "edit" buttons back to the app bar * tweaks * Updates to drawer navigation menu * home screen improvements * text tweaks * Fix appBarTitle for notifications widget * Update "tabs" for category display * Fix for attachment widget * Update tabs for purchaseorder view * Update part display * Cleanup * Add "BOM" tab to part detail widget * Paginated list search cleanup * Update release notes * Update old function * linting * linting * Tweaks to bottomappbar - Increase icon size slightly - Adjust "actions" icon
This commit is contained in:
@ -1,4 +1,5 @@
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
|
||||
@ -43,42 +44,34 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
_StockItemDisplayState();
|
||||
|
||||
@override
|
||||
String getAppBarTitle(BuildContext context) => L10().stockItem;
|
||||
String getAppBarTitle() => L10().stockItem;
|
||||
|
||||
bool stockShowHistory = false;
|
||||
|
||||
@override
|
||||
List<Widget> getAppBarActions(BuildContext context) {
|
||||
|
||||
List<Widget> appBarActions(BuildContext context) {
|
||||
List<Widget> actions = [];
|
||||
|
||||
if (InvenTreeAPI().checkPermission("stock", "view")) {
|
||||
actions.add(
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.globe),
|
||||
onPressed: _openInvenTreePage,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (InvenTreeAPI().supportsMixin("locate")) {
|
||||
actions.add(
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.magnifyingGlassLocation),
|
||||
tooltip: L10().locateItem,
|
||||
onPressed: () async {
|
||||
InvenTreeAPI().locateItemOrLocation(context, item: widget.item.pk);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (InvenTreeAPI().checkPermission("stock", "change")) {
|
||||
if (api.supportsMixin("locate")) {
|
||||
actions.add(
|
||||
IconButton(
|
||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
||||
tooltip: L10().edit,
|
||||
onPressed: () { _editStockItem(context); },
|
||||
icon: Icon(Icons.travel_explore),
|
||||
tooltip: L10().locateItem,
|
||||
onPressed: () async {
|
||||
api.locateItemOrLocation(context, item: widget.item.pk);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (api.checkPermission("stock", "change")) {
|
||||
actions.add(
|
||||
IconButton(
|
||||
icon: Icon(Icons.edit_square),
|
||||
tooltip: L10().editItem,
|
||||
onPressed: () {
|
||||
_editStockItem(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -86,8 +79,115 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
return actions;
|
||||
}
|
||||
|
||||
Future<void> _openInvenTreePage() async {
|
||||
widget.item.goToInvenTreePage();
|
||||
@override
|
||||
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||
|
||||
List<SpeedDialChild> actions = [];
|
||||
|
||||
if (api.checkPermission("stock", "change")) {
|
||||
|
||||
// Stock adjustment actions available if item is *not* serialized
|
||||
if (!widget.item.isSerialized()) {
|
||||
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.circleCheck, color: Colors.blue),
|
||||
label: L10().countStock,
|
||||
onTap: _countStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.circleMinus, color: Colors.red),
|
||||
label: L10().removeStock,
|
||||
onTap: _removeStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.circlePlus, color: Colors.green),
|
||||
label: L10().addStock,
|
||||
onTap: _addStockDialog,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Transfer item
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.trolley),
|
||||
label: L10().transferStock,
|
||||
onTap: () {
|
||||
_transferStockDialog(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (labels.isNotEmpty) {
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.print),
|
||||
label: L10().printLabel,
|
||||
onTap: () {
|
||||
_printLabel(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (api.checkPermission("stock", "delete")) {
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: FaIcon(FontAwesomeIcons.trashCan, color: Colors.red),
|
||||
label: L10().stockItemDelete,
|
||||
onTap: () {
|
||||
_deleteItem(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
@override
|
||||
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||
List<SpeedDialChild> actions = [];
|
||||
|
||||
if (api.checkPermission("stock", "change")) {
|
||||
// Scan item into location
|
||||
actions.add(
|
||||
SpeedDialChild(
|
||||
child: Icon(Icons.qr_code_scanner),
|
||||
label: L10().scanIntoLocation,
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) =>
|
||||
InvenTreeQRView(
|
||||
StockItemScanIntoLocationHandler(widget.item)))
|
||||
).then((ctx) {
|
||||
refresh(context);
|
||||
});
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
if (api.supportModernBarcodes) {
|
||||
actions.add(
|
||||
customBarcodeAction(
|
||||
context, this,
|
||||
widget.item.customBarcode,
|
||||
"stockitem", widget.item.pk
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
// Is label printing enabled for this StockItem?
|
||||
@ -740,170 +840,13 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
return tiles;
|
||||
}
|
||||
|
||||
List<Widget> actionTiles(BuildContext context) {
|
||||
List<Widget> tiles = [];
|
||||
|
||||
tiles.add(headerTile());
|
||||
|
||||
// First check that the user has the required permissions to adjust stock
|
||||
if (!InvenTreeAPI().checkPermission("stock", "change")) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().permissionRequired),
|
||||
leading: FaIcon(FontAwesomeIcons.userXmark)
|
||||
)
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
subtitle: Text(L10().permissionAccountDenied),
|
||||
)
|
||||
);
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
// "Count" is not available for serialized stock
|
||||
if (!widget.item.isSerialized()) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().countStock),
|
||||
leading: FaIcon(FontAwesomeIcons.circleCheck, color: COLOR_CLICK),
|
||||
onTap: _countStockDialog,
|
||||
trailing: Text(widget.item.quantityString(includeUnits: true)),
|
||||
)
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().removeStock),
|
||||
leading: FaIcon(FontAwesomeIcons.circleMinus, color: COLOR_CLICK),
|
||||
onTap: _removeStockDialog,
|
||||
)
|
||||
);
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().addStock),
|
||||
leading: FaIcon(FontAwesomeIcons.circlePlus, color: COLOR_CLICK),
|
||||
onTap: _addStockDialog,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().transferStock),
|
||||
subtitle: Text(L10().transferStockDetail),
|
||||
leading: FaIcon(FontAwesomeIcons.rightLeft, color: COLOR_CLICK),
|
||||
onTap: () { _transferStockDialog(context); },
|
||||
)
|
||||
);
|
||||
|
||||
// Scan item into a location
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().scanIntoLocation),
|
||||
subtitle: Text(L10().scanIntoLocationDetail),
|
||||
leading: FaIcon(FontAwesomeIcons.rightLeft, color: COLOR_CLICK),
|
||||
trailing: Icon(Icons.qr_code_scanner),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemScanIntoLocationHandler(widget.item)))
|
||||
).then((ctx) {
|
||||
refresh(context);
|
||||
});
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
if (InvenTreeAPI().supportModernBarcodes || widget.item.customBarcode.isEmpty) {
|
||||
tiles.add(customBarcodeActionTile(context, this, widget.item.customBarcode, "stockitem", widget.item.pk));
|
||||
} else {
|
||||
// Note: Custom legacy barcodes (only for StockItem model) are handled differently
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().barcodeUnassign),
|
||||
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
|
||||
onTap: () async {
|
||||
await widget.item.update(values: {"uid": ""});
|
||||
refresh(context);
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Print label (if label printing plugins exist)
|
||||
if (labels.isNotEmpty) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text(L10().printLabel),
|
||||
leading: FaIcon(FontAwesomeIcons.print, color: COLOR_CLICK),
|
||||
onTap: () {
|
||||
_printLabel(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// If the user has permission to delete this stock item
|
||||
if (InvenTreeAPI().checkPermission("stock", "delete")) {
|
||||
tiles.add(
|
||||
ListTile(
|
||||
title: Text("Delete Stock Item"),
|
||||
leading: FaIcon(FontAwesomeIcons.trashCan, color: COLOR_DANGER),
|
||||
onTap: () {
|
||||
_deleteItem(context);
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return tiles;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBottomNavBar(BuildContext context) {
|
||||
return BottomNavigationBar(
|
||||
currentIndex: tabIndex,
|
||||
onTap: onTabSelectionChanged,
|
||||
items: <BottomNavigationBarItem> [
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
||||
label: L10().details,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
||||
label: L10().actions, ),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
Widget getSelectedWidget(int index) {
|
||||
switch (index) {
|
||||
case 0:
|
||||
return ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: detailTiles()
|
||||
).toList(),
|
||||
);
|
||||
case 1:
|
||||
return ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: actionTiles(context)
|
||||
).toList()
|
||||
);
|
||||
default:
|
||||
return ListView();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget getBody(BuildContext context) {
|
||||
return getSelectedWidget(tabIndex);
|
||||
return ListView(
|
||||
children: ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: detailTiles()
|
||||
).toList()
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user