mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-27 04:56:48 +00:00
Scanner wedge mode (#437)
* Add code_scan_listener package * Implement wedge controller widget * Update barcode settings widget - Allow user to choose which barcode scanner to use * Fix typo * Select barcode scanner widget based on user preference * Fix rendering issues for wedge controller * Update release notes * Add unit test for wedge scanner widget - Required some tweaks to other code * Use better library - https://github.com/fuadreza/flutter_barcode_listener - Fork of https://github.com/shaxxx/flutter_barcode_listener - Properly handles key "case" issues (shift, essentially) - Verified that it works correctly for multiple character types * Local copy of code, rather than relying on package which is not available on pub.dev * Fix unit test
This commit is contained in:
parent
b6ab9d5da5
commit
c641cea369
@ -79,3 +79,4 @@ linter:
|
|||||||
no_leading_underscores_for_local_identifiers: false
|
no_leading_underscores_for_local_identifiers: false
|
||||||
|
|
||||||
use_super_parameters: false
|
use_super_parameters: false
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
### 0.13.0 - October 2023
|
### 0.13.0 - October 2023
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- Adds "wedge scanner" mode, allowing use with external barcode readers
|
||||||
- Add ability to scan in received items using supplier barcodes
|
- Add ability to scan in received items using supplier barcodes
|
||||||
- Store API token, rather than username:password
|
- Store API token, rather than username:password
|
||||||
- Ensure that user will lose access if token is revoked by server
|
- Ensure that user will lose access if token is revoked by server
|
||||||
|
@ -6,6 +6,11 @@ const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1);
|
|||||||
// Return an "action" color based on the current theme
|
// Return an "action" color based on the current theme
|
||||||
Color get COLOR_ACTION {
|
Color get COLOR_ACTION {
|
||||||
|
|
||||||
|
// OneContext might not have context, e.g. in testing
|
||||||
|
if (!OneContext.hasContext) {
|
||||||
|
return Colors.lightBlue;
|
||||||
|
}
|
||||||
|
|
||||||
BuildContext? context = OneContext().context;
|
BuildContext? context = OneContext().context;
|
||||||
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
|
@ -2,6 +2,7 @@ import "package:flutter/material.dart";
|
|||||||
|
|
||||||
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
import "package:inventree/preferences.dart";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
|
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ import "package:inventree/helpers.dart";
|
|||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
import "package:inventree/barcode/camera_controller.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/controller.dart";
|
||||||
import "package:inventree/barcode/handler.dart";
|
import "package:inventree/barcode/handler.dart";
|
||||||
import "package:inventree/barcode/tones.dart";
|
import "package:inventree/barcode/tones.dart";
|
||||||
@ -44,6 +46,19 @@ Future<Object?> scanBarcode(BuildContext context, {BarcodeHandler? handler}) asy
|
|||||||
|
|
||||||
InvenTreeBarcodeController controller = CameraBarcodeController(handler);
|
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(
|
return Navigator.of(context).push(
|
||||||
PageRouteBuilder(
|
PageRouteBuilder(
|
||||||
pageBuilder: (context, _, __) => controller,
|
pageBuilder: (context, _, __) => controller,
|
||||||
|
@ -58,9 +58,9 @@ class InvenTreeBarcodeControllerState extends State<InvenTreeBarcodeController>
|
|||||||
processingBarcode = true;
|
processingBarcode = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
BuildContext? context = OneContext().context;
|
BuildContext? context = OneContext.hasContext ? OneContext().context : null;
|
||||||
|
|
||||||
showLoadingOverlay(context!);
|
showLoadingOverlay(context);
|
||||||
await pauseScan();
|
await pauseScan();
|
||||||
|
|
||||||
await widget.handler.processBarcode(data);
|
await widget.handler.processBarcode(data);
|
||||||
|
175
lib/barcode/flutter_barcode_listener.dart
Normal file
175
lib/barcode/flutter_barcode_listener.dart
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
|
||||||
|
/*
|
||||||
|
* Custom keyboard listener which allows the app to act as a keyboard "wedge",
|
||||||
|
* and intercept barcodes from any compatible scanner.
|
||||||
|
*
|
||||||
|
* Note: This code was copied from https://github.com/fuadreza/flutter_barcode_listener/blob/master/lib/flutter_barcode_listener.dart
|
||||||
|
*
|
||||||
|
* If that code becomes available on pub.dev, we can remove this file and reference that library
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter/services.dart";
|
||||||
|
|
||||||
|
typedef BarcodeScannedCallback = void Function(String barcode);
|
||||||
|
|
||||||
|
/// This widget will listen for raw PHYSICAL keyboard events
|
||||||
|
/// even when other controls have primary focus.
|
||||||
|
/// It will buffer all characters coming in specifed `bufferDuration` time frame
|
||||||
|
/// that end with line feed character and call callback function with result.
|
||||||
|
/// Keep in mind this widget will listen for events even when not visible.
|
||||||
|
/// Windows seems to be using the [RawKeyDownEvent] instead of the
|
||||||
|
/// [RawKeyUpEvent], this behaviour can be managed by setting [useKeyDownEvent].
|
||||||
|
class BarcodeKeyboardListener extends StatefulWidget {
|
||||||
|
|
||||||
|
/// This widget will listen for raw PHYSICAL keyboard events
|
||||||
|
/// even when other controls have primary focus.
|
||||||
|
/// It will buffer all characters coming in specifed `bufferDuration` time frame
|
||||||
|
/// that end with line feed character and call callback function with result.
|
||||||
|
/// Keep in mind this widget will listen for events even when not visible.
|
||||||
|
const BarcodeKeyboardListener(
|
||||||
|
{Key? key,
|
||||||
|
|
||||||
|
/// Child widget to be displayed.
|
||||||
|
required this.child,
|
||||||
|
|
||||||
|
/// Callback to be called when barcode is scanned.
|
||||||
|
required Function(String) onBarcodeScanned,
|
||||||
|
|
||||||
|
/// When experiencing issueswith empty barcodes on Windows,
|
||||||
|
/// set this value to true. Default value is `false`.
|
||||||
|
this.useKeyDownEvent = false,
|
||||||
|
|
||||||
|
/// Maximum time between two key events.
|
||||||
|
/// If time between two key events is longer than this value
|
||||||
|
/// previous keys will be ignored.
|
||||||
|
Duration bufferDuration = hundredMs})
|
||||||
|
: _onBarcodeScanned = onBarcodeScanned,
|
||||||
|
_bufferDuration = bufferDuration,
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
final Widget child;
|
||||||
|
final BarcodeScannedCallback _onBarcodeScanned;
|
||||||
|
final Duration _bufferDuration;
|
||||||
|
final bool useKeyDownEvent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
_BarcodeKeyboardListenerState createState() => _BarcodeKeyboardListenerState(
|
||||||
|
_onBarcodeScanned, _bufferDuration, useKeyDownEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Duration aSecond = Duration(seconds: 1);
|
||||||
|
const Duration hundredMs = Duration(milliseconds: 100);
|
||||||
|
const String lineFeed = "\n";
|
||||||
|
|
||||||
|
class _BarcodeKeyboardListenerState extends State<BarcodeKeyboardListener> {
|
||||||
|
|
||||||
|
_BarcodeKeyboardListenerState(this._onBarcodeScannedCallback,
|
||||||
|
this._bufferDuration, this._useKeyDownEvent) {
|
||||||
|
RawKeyboard.instance.addListener(_keyBoardCallback);
|
||||||
|
_keyboardSubscription =
|
||||||
|
_controller.stream.where((char) => char != null).listen(onKeyEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _scannedChars = [];
|
||||||
|
DateTime? _lastScannedCharCodeTime;
|
||||||
|
late StreamSubscription<String?> _keyboardSubscription;
|
||||||
|
|
||||||
|
final BarcodeScannedCallback _onBarcodeScannedCallback;
|
||||||
|
final Duration _bufferDuration;
|
||||||
|
|
||||||
|
final _controller = StreamController<String?>();
|
||||||
|
|
||||||
|
final bool _useKeyDownEvent;
|
||||||
|
|
||||||
|
bool _isShiftPressed = false;
|
||||||
|
void onKeyEvent(String? char) {
|
||||||
|
//remove any pending characters older than bufferDuration value
|
||||||
|
checkPendingCharCodesToClear();
|
||||||
|
_lastScannedCharCodeTime = DateTime.now();
|
||||||
|
if (char == lineFeed) {
|
||||||
|
_onBarcodeScannedCallback.call(_scannedChars.join());
|
||||||
|
resetScannedCharCodes();
|
||||||
|
} else {
|
||||||
|
//add character to list of scanned characters;
|
||||||
|
_scannedChars.add(char!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void checkPendingCharCodesToClear() {
|
||||||
|
if (_lastScannedCharCodeTime != null) {
|
||||||
|
if (_lastScannedCharCodeTime!
|
||||||
|
.isBefore(DateTime.now().subtract(_bufferDuration))) {
|
||||||
|
resetScannedCharCodes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetScannedCharCodes() {
|
||||||
|
_lastScannedCharCodeTime = null;
|
||||||
|
_scannedChars = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
void addScannedCharCode(String charCode) {
|
||||||
|
_scannedChars.add(charCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _keyBoardCallback(RawKeyEvent keyEvent) {
|
||||||
|
if (keyEvent.logicalKey.keyId > 255 &&
|
||||||
|
keyEvent.data.logicalKey != LogicalKeyboardKey.enter &&
|
||||||
|
keyEvent.data.logicalKey != LogicalKeyboardKey.shiftLeft) return;
|
||||||
|
if ((!_useKeyDownEvent && keyEvent is RawKeyUpEvent) ||
|
||||||
|
(_useKeyDownEvent && keyEvent is RawKeyDownEvent)) {
|
||||||
|
if (keyEvent.data is RawKeyEventDataAndroid) {
|
||||||
|
if (keyEvent.data.logicalKey == LogicalKeyboardKey.shiftLeft) {
|
||||||
|
_isShiftPressed = true;
|
||||||
|
} else {
|
||||||
|
if (_isShiftPressed) {
|
||||||
|
_isShiftPressed = false;
|
||||||
|
_controller.sink.add(String.fromCharCode(
|
||||||
|
((keyEvent.data) as RawKeyEventDataAndroid).codePoint).toUpperCase());
|
||||||
|
} else {
|
||||||
|
_controller.sink.add(String.fromCharCode(
|
||||||
|
((keyEvent.data) as RawKeyEventDataAndroid).codePoint));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataFuchsia) {
|
||||||
|
_controller.sink.add(String.fromCharCode(
|
||||||
|
((keyEvent.data) as RawKeyEventDataFuchsia).codePoint));
|
||||||
|
} else if (keyEvent.data.logicalKey == LogicalKeyboardKey.enter) {
|
||||||
|
_controller.sink.add(lineFeed);
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataWeb) {
|
||||||
|
_controller.sink.add(((keyEvent.data) as RawKeyEventDataWeb).keyLabel);
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataLinux) {
|
||||||
|
_controller.sink
|
||||||
|
.add(((keyEvent.data) as RawKeyEventDataLinux).keyLabel);
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataWindows) {
|
||||||
|
_controller.sink.add(String.fromCharCode(
|
||||||
|
((keyEvent.data) as RawKeyEventDataWindows).keyCode));
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataMacOs) {
|
||||||
|
_controller.sink
|
||||||
|
.add(((keyEvent.data) as RawKeyEventDataMacOs).characters);
|
||||||
|
} else if (keyEvent.data is RawKeyEventDataIos) {
|
||||||
|
_controller.sink
|
||||||
|
.add(((keyEvent.data) as RawKeyEventDataIos).characters);
|
||||||
|
} else {
|
||||||
|
_controller.sink.add(keyEvent.character);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_keyboardSubscription.cancel();
|
||||||
|
_controller.close();
|
||||||
|
RawKeyboard.instance.removeListener(_keyBoardCallback);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
102
lib/barcode/wedge_controller.dart
Normal file
102
lib/barcode/wedge_controller.dart
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
|
import "package:inventree/app_colors.dart";
|
||||||
|
import "package:inventree/barcode/controller.dart";
|
||||||
|
import "package:inventree/barcode/handler.dart";
|
||||||
|
import "package:inventree/barcode/flutter_barcode_listener.dart";
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/helpers.dart";
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Barcode controller which acts as a keyboard wedge,
|
||||||
|
* intercepting barcode data which is entered as rapid keyboard presses
|
||||||
|
*/
|
||||||
|
class WedgeBarcodeController extends InvenTreeBarcodeController {
|
||||||
|
|
||||||
|
const WedgeBarcodeController(BarcodeHandler handler, {Key? key}) : super(handler, key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _WedgeBarcodeControllerState();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
||||||
|
|
||||||
|
_WedgeBarcodeControllerState() : super();
|
||||||
|
|
||||||
|
bool canScan = true;
|
||||||
|
|
||||||
|
bool get scanning => mounted && canScan;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> pauseScan() async {
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
canScan = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> resumeScan() async {
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
canScan = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(L10().scanBarcode),
|
||||||
|
),
|
||||||
|
backgroundColor: Colors.black.withOpacity(0.9),
|
||||||
|
body: Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Spacer(flex: 5),
|
||||||
|
FaIcon(FontAwesomeIcons.barcode, size: 64),
|
||||||
|
Spacer(flex: 5),
|
||||||
|
BarcodeKeyboardListener(
|
||||||
|
useKeyDownEvent: true,
|
||||||
|
child: SizedBox(
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
color: scanning ? COLOR_ACTION : COLOR_PROGRESS
|
||||||
|
),
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
),
|
||||||
|
onBarcodeScanned: (String barcode) {
|
||||||
|
debug("scanned: ${barcode}");
|
||||||
|
if (scanning) {
|
||||||
|
// Process the barcode data
|
||||||
|
handleBarcodeData(barcode);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Spacer(flex: 5),
|
||||||
|
Padding(
|
||||||
|
child: Text(
|
||||||
|
widget.handler.getOverlayText(context),
|
||||||
|
style: TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Colors.white)
|
||||||
|
),
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -118,6 +118,12 @@
|
|||||||
"barcodeScanAssign": "Scan to assign barcode",
|
"barcodeScanAssign": "Scan to assign barcode",
|
||||||
"@barcodeScanAssign": {},
|
"@barcodeScanAssign": {},
|
||||||
|
|
||||||
|
"barcodeScanController": "Scanner Input",
|
||||||
|
"@barcodeScanController": {},
|
||||||
|
|
||||||
|
"barcodeScanControllerDetail": "Select barcode scanner input source",
|
||||||
|
"@barcodeScanControllerDetail": {},
|
||||||
|
|
||||||
"barcodeScanDelay": "Barcode Scan Delay",
|
"barcodeScanDelay": "Barcode Scan Delay",
|
||||||
"@barcodeScanDelay": {},
|
"@barcodeScanDelay": {},
|
||||||
|
|
||||||
@ -169,6 +175,12 @@
|
|||||||
"building": "Building",
|
"building": "Building",
|
||||||
"@building": {},
|
"@building": {},
|
||||||
|
|
||||||
|
"cameraInternal": "Internal Camera",
|
||||||
|
"@cameraInternal": {},
|
||||||
|
|
||||||
|
"cameraInternalDetail": "Use internal camera to read barcodes",
|
||||||
|
"@cameraInternalDetail": {},
|
||||||
|
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"@cancel": {
|
"@cancel": {
|
||||||
"description": "Cancel"
|
"description": "Cancel"
|
||||||
@ -1003,6 +1015,12 @@
|
|||||||
"scanIntoLocationDetail": "Scan this item into location",
|
"scanIntoLocationDetail": "Scan this item into location",
|
||||||
"@scanIntoLocationDetail": {},
|
"@scanIntoLocationDetail": {},
|
||||||
|
|
||||||
|
"scannerExternal": "External Scanner",
|
||||||
|
"@scannerExternal": {},
|
||||||
|
|
||||||
|
"scannerExternalDetail": "Use external scanner to read barcodes (wedge mode)",
|
||||||
|
"@scannerExternalDetail": {},
|
||||||
|
|
||||||
"scanReceivedParts": "Scan Received Parts",
|
"scanReceivedParts": "Scan Received Parts",
|
||||||
"@scanReceivedParts": {},
|
"@scanReceivedParts": {},
|
||||||
|
|
||||||
|
@ -40,6 +40,11 @@ const String INV_STRICT_HTTPS = "strictHttps";
|
|||||||
|
|
||||||
// Barcode settings
|
// Barcode settings
|
||||||
const String INV_BARCODE_SCAN_DELAY = "barcodeScanDelay";
|
const String INV_BARCODE_SCAN_DELAY = "barcodeScanDelay";
|
||||||
|
const String INV_BARCODE_SCAN_TYPE = "barcodeScanType";
|
||||||
|
|
||||||
|
// Barcode scanner types
|
||||||
|
const int BARCODE_CONTROLLER_CAMERA = 0;
|
||||||
|
const int BARCODE_CONTROLLER_WEDGE = 1;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Class for storing InvenTree preferences in a NoSql DB
|
* Class for storing InvenTree preferences in a NoSql DB
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
import "package:inventree/preferences.dart";
|
import "package:inventree/preferences.dart";
|
||||||
|
import "package:inventree/widget/dialogs.dart";
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeBarcodeSettingsWidget extends StatefulWidget {
|
class InvenTreeBarcodeSettingsWidget extends StatefulWidget {
|
||||||
@ -15,6 +17,7 @@ class _InvenTreeBarcodeSettingsState extends State<InvenTreeBarcodeSettingsWidge
|
|||||||
_InvenTreeBarcodeSettingsState();
|
_InvenTreeBarcodeSettingsState();
|
||||||
|
|
||||||
int barcodeScanDelay = 500;
|
int barcodeScanDelay = 500;
|
||||||
|
int barcodeScanType = BARCODE_CONTROLLER_CAMERA;
|
||||||
|
|
||||||
final TextEditingController _barcodeScanDelayController = TextEditingController();
|
final TextEditingController _barcodeScanDelayController = TextEditingController();
|
||||||
|
|
||||||
@ -26,6 +29,7 @@ class _InvenTreeBarcodeSettingsState extends State<InvenTreeBarcodeSettingsWidge
|
|||||||
|
|
||||||
Future<void> loadSettings() async {
|
Future<void> loadSettings() async {
|
||||||
barcodeScanDelay = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500) as int;
|
barcodeScanDelay = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500) as int;
|
||||||
|
barcodeScanType = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int;
|
||||||
|
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -89,11 +93,55 @@ class _InvenTreeBarcodeSettingsState extends State<InvenTreeBarcodeSettingsWidge
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
// Construct an icon for the barcode scanner input
|
||||||
|
Widget? barcodeInputIcon;
|
||||||
|
|
||||||
|
switch (barcodeScanType) {
|
||||||
|
case BARCODE_CONTROLLER_WEDGE:
|
||||||
|
barcodeInputIcon = Icon(Icons.barcode_reader);
|
||||||
|
break;
|
||||||
|
case BARCODE_CONTROLLER_CAMERA:
|
||||||
|
default:
|
||||||
|
barcodeInputIcon = FaIcon(FontAwesomeIcons.camera);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(L10().barcodes)),
|
appBar: AppBar(title: Text(L10().barcodes)),
|
||||||
body: Container(
|
body: Container(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().barcodeScanController),
|
||||||
|
subtitle: Text(L10().barcodeScanControllerDetail),
|
||||||
|
leading: Icon(Icons.qr_code_scanner),
|
||||||
|
trailing: barcodeInputIcon,
|
||||||
|
onTap: () async {
|
||||||
|
choiceDialog(
|
||||||
|
L10().barcodeScanController,
|
||||||
|
[
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().cameraInternal),
|
||||||
|
subtitle: Text(L10().cameraInternalDetail),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.camera),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().scannerExternal),
|
||||||
|
subtitle: Text(L10().scannerExternalDetail),
|
||||||
|
leading: Icon(Icons.barcode_reader),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
onSelected: (idx) async {
|
||||||
|
barcodeScanType = idx as int;
|
||||||
|
InvenTreeSettingsManager().setValue(INV_BARCODE_SCAN_TYPE, barcodeScanType);
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().barcodeScanDelay),
|
title: Text(L10().barcodeScanDelay),
|
||||||
subtitle: Text(L10().barcodeScanDelayDetail),
|
subtitle: Text(L10().barcodeScanDelayDetail),
|
||||||
@ -104,7 +152,7 @@ class _InvenTreeBarcodeSettingsState extends State<InvenTreeBarcodeSettingsWidge
|
|||||||
_editBarcodeScanDelay(context);
|
_editBarcodeScanDelay(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -14,7 +14,12 @@ Widget progressIndicator() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void showLoadingOverlay(BuildContext context) {
|
void showLoadingOverlay(BuildContext? context) {
|
||||||
|
|
||||||
|
if (context == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Loader.show(
|
Loader.show(
|
||||||
context,
|
context,
|
||||||
themeData: Theme.of(context).copyWith(colorScheme: ColorScheme.fromSwatch())
|
themeData: Theme.of(context).copyWith(colorScheme: ColorScheme.fromSwatch())
|
||||||
|
32
test/wedge_scanner_test.dart
Normal file
32
test/wedge_scanner_test.dart
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter/services.dart";
|
||||||
|
import "package:flutter_test/flutter_test.dart";
|
||||||
|
import "package:inventree/barcode/barcode.dart";
|
||||||
|
import "package:inventree/barcode/wedge_controller.dart";
|
||||||
|
import "package:inventree/helpers.dart";
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets("Wedge Scanner Test", (tester) async {
|
||||||
|
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: WedgeBarcodeController(BarcodeScanHandler())
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Generate some keyboard data
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyB);
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.keyC);
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.enter);
|
||||||
|
|
||||||
|
// Check debug output
|
||||||
|
debugContains("scanned: abc");
|
||||||
|
debugContains("No match for barcode");
|
||||||
|
debugContains("Server Error");
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user