import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/helpers.dart"; import "package:inventree/inventree/sales_order.dart"; import "package:inventree/preferences.dart"; import "package:inventree/widget/order/sales_order_detail.dart"; import "package:one_context/one_context.dart"; import "package:inventree/api.dart"; import "package:inventree/l10.dart"; import "package:inventree/barcode/camera_controller.dart"; import "package:inventree/barcode/wedge_controller.dart"; import "package:inventree/barcode/controller.dart"; import "package:inventree/barcode/handler.dart"; import "package:inventree/barcode/tones.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/part.dart"; import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/inventree/stock.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/stock/location_display.dart"; import "package:inventree/widget/part/part_detail.dart"; import "package:inventree/widget/order/purchase_order_detail.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock/stock_detail.dart"; import "package:inventree/widget/company/supplier_part_detail.dart"; // Signal a barcode scan success to the user Future barcodeSuccess(String msg) async { barcodeSuccessTone(); showSnackIcon(msg, success: true); } // Signal a barcode scan failure to the user Future barcodeFailure(String msg, dynamic extra) async { barcodeFailureTone(); showSnackIcon( msg, success: false, onAction: () { if (hasContext()) { OneContext().showDialog( builder: (BuildContext context) => SimpleDialog( title: Text(L10().barcodeError), children: [ ListTile( title: Text(L10().responseData), subtitle: Text(extra.toString()) ) ] ) ); } } ); } /* * Launch a barcode scanner with a particular context and handler. * * - Can be called with a custom BarcodeHandler instance, or use the default handler * - Returns a Future which resolves when the scanner is dismissed * - The provided BarcodeHandler instance is used to handle the scanned barcode */ Future scanBarcode(BuildContext context, {BarcodeHandler? handler}) async { // Default to generic scan handler handler ??= BarcodeScanHandler(); InvenTreeBarcodeController controller = CameraBarcodeController(handler); // Select barcode controller based on user preference final int barcodeControllerType = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int; switch (barcodeControllerType) { case BARCODE_CONTROLLER_WEDGE: controller = WedgeBarcodeController(handler); break; case BARCODE_CONTROLLER_CAMERA: default: // Already set as default option break; } return Navigator.of(context).push( PageRouteBuilder( pageBuilder: (context, _, __) => controller, opaque: false, ) ); } /* * Class for general barcode scanning. * Scan *any* barcode without context, and then redirect app to correct view. * * Handles scanning of: * * - StockLocation * - StockItem * - Part * - SupplierPart * - PurchaseOrder */ class BarcodeScanHandler extends BarcodeHandler { @override String getOverlayText(BuildContext context) => L10().barcodeScanGeneral; @override Future onBarcodeUnknown(Map data) async { barcodeFailureTone(); showSnackIcon( L10().barcodeNoMatch, icon: TablerIcons.exclamation_circle, success: false, ); } /* * Response when a "Part" instance is scanned */ Future handlePart(int pk) async { var part = await InvenTreePart().get(pk); if (part is InvenTreePart) { OneContext().pop(); OneContext().push(MaterialPageRoute(builder: (context) => PartDetailWidget(part))); } } /* * Response when a "StockItem" instance is scanned */ Future handleStockItem(int pk) async { var item = await InvenTreeStockItem().get(pk); if (item is InvenTreeStockItem) { OneContext().pop(); OneContext().push(MaterialPageRoute( builder: (context) => StockDetailWidget(item))); } } /* * Response when a "StockLocation" instance is scanned */ Future handleStockLocation(int pk) async { var loc = await InvenTreeStockLocation().get(pk); if (loc is InvenTreeStockLocation) { OneContext().pop(); OneContext().navigator.push(MaterialPageRoute( builder: (context) => LocationDisplayWidget(loc))); } } /* * Response when a "SupplierPart" instance is scanned */ Future handleSupplierPart(int pk) async { var supplierpart = await InvenTreeSupplierPart().get(pk); if (supplierpart is InvenTreeSupplierPart) { OneContext().pop(); OneContext().push(MaterialPageRoute( builder: (context) => SupplierPartDetailWidget(supplierpart))); } } /* * Response when a "PurchaseOrder" instance is scanned */ Future handlePurchaseOrder(int pk) async { var order = await InvenTreePurchaseOrder().get(pk); if (order is InvenTreePurchaseOrder) { OneContext().pop(); OneContext().push(MaterialPageRoute( builder: (context) => PurchaseOrderDetailWidget(order))); } } // Response when a SalesOrder instance is scanned Future handleSalesOrder(int pk) async { var order = await InvenTreeSalesOrder().get(pk); if (order is InvenTreeSalesOrder) { OneContext().pop(); OneContext().push(MaterialPageRoute( builder: (context) => SalesOrderDetailWidget(order))); } } @override Future onBarcodeMatched(Map data) async { int pk = -1; String model = ""; // The following model types can be matched with barcodes List validModels = [ "part", "stockitem", "stocklocation", "supplierpart", ]; if (InvenTreeAPI().supportsOrderBarcodes) { validModels.add("purchaseorder"); validModels.add("salesorder"); } for (var key in validModels) { if (data.containsKey(key)) { pk = (data[key]?["pk"] ?? -1) as int; // Break on the first valid match found if (pk > 0) { model = key; break; } } } // A valid result has been found if (pk > 0 && model.isNotEmpty) { barcodeSuccessTone(); switch (model) { case "part": await handlePart(pk); return; case "stockitem": await handleStockItem(pk); return; case "stocklocation": await handleStockLocation(pk); return; case "supplierpart": await handleSupplierPart(pk); return; case "purchaseorder": await handlePurchaseOrder(pk); return; case "salesorder": await handleSalesOrder(pk); return; // TODO: Handle manufacturer part default: // Fall through to failure state break; } } // If we get here, we have not found a valid barcode result! barcodeFailureTone(); showSnackIcon( L10().barcodeUnknown, success: false, onAction: () { if (hasContext()) { OneContext().showDialog( builder: (BuildContext context) => SimpleDialog( title: Text(L10().unknownResponse), children: [ ListTile( title: Text(L10().responseData), subtitle: Text(data.toString()), ) ], ) ); } } ); } } /* * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) */ class UniqueBarcodeHandler extends BarcodeHandler { 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) { if (overlayText.isEmpty) { return L10().barcodeScanAssign; } else { return overlayText; } } @override Future onBarcodeMatched(Map data) async { barcodeFailureTone(); // If the barcode is known, we can"t assign it to the stock item! showSnackIcon( L10().barcodeInUse, icon: Icons.qr_code, success: false ); } @override Future onBarcodeUnknown(Map data) async { // If the barcode is unknown, we *can* assign it to the stock item! if (!data.containsKey("hash") && !data.containsKey("barcode_hash")) { showServerError( "barcode/", L10().missingData, L10().barcodeMissingHash, ); } else { String barcode; barcode = (data["barcode_data"] ?? "") as String; if (barcode.isEmpty) { barcodeFailureTone(); showSnackIcon( L10().barcodeError, success: false, ); } else { barcodeSuccessTone(); // Close the barcode scanner if (OneContext.hasContext) { OneContext().pop(); } callback(barcode); } } } } SpeedDialChild customBarcodeAction(BuildContext context, RefreshableState state, String barcode, String model, int pk) { if (barcode.isEmpty) { return SpeedDialChild( label: L10().barcodeAssign, child: Icon(Icons.barcode_reader), onTap: () { var handler = UniqueBarcodeHandler((String barcode) { InvenTreeAPI().linkBarcode({ model: pk.toString(), "barcode": barcode, }).then((bool result) { showSnackIcon( result ? L10().barcodeAssigned : L10().barcodeNotAssigned, success: result ); state.refresh(context); }); }); scanBarcode(context, handler: handler); } ); } else { return SpeedDialChild( child: Icon(Icons.barcode_reader), label: L10().barcodeUnassign, onTap: () { InvenTreeAPI().unlinkBarcode({ model: pk.toString() }).then((bool result) { showSnackIcon( result ? L10().requestSuccessful : L10().requestFailed, success: result, ); state.refresh(context); }); } ); } }