mirror of
https://github.com/inventree/inventree-app.git
synced 2026-09-09 03:10:14 +00:00
Add intent wedge scanner, MDM support for Android (#863)
Add Android intent wedge scanner support with auto configuration for supported Zebra and Datalogic devices. Intent wedge scans can be done without pressing the scan button first. Adds the ability for an MDM server to push settings on Android.
This commit is contained in:
@@ -18,6 +18,7 @@ import "package:inventree/api.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
|
||||
import "package:inventree/barcode/camera_controller.dart";
|
||||
import "package:inventree/barcode/intent_controller.dart";
|
||||
import "package:inventree/barcode/wedge_controller.dart";
|
||||
import "package:inventree/barcode/controller.dart";
|
||||
import "package:inventree/barcode/handler.dart";
|
||||
@@ -68,6 +69,53 @@ Future<void> barcodeFailure(String msg, dynamic extra) async {
|
||||
);
|
||||
}
|
||||
|
||||
void initGlobalIntentListener() {
|
||||
bool _processing = false;
|
||||
|
||||
datawedgeStream.listen((event) async {
|
||||
if (intentScannerActive) return;
|
||||
if (_processing) return;
|
||||
|
||||
_processing = true;
|
||||
|
||||
try {
|
||||
final int controllerType =
|
||||
await InvenTreeSettingsManager().getValue(
|
||||
INV_BARCODE_SCAN_TYPE,
|
||||
BARCODE_CONTROLLER_CAMERA,
|
||||
)
|
||||
as int;
|
||||
if (controllerType != BARCODE_CONTROLLER_INTENT) return;
|
||||
|
||||
if (!InvenTreeAPI().isConnected()) return;
|
||||
|
||||
String barcode = "";
|
||||
if (event is Map) {
|
||||
final map = Map<String, dynamic>.from(event);
|
||||
barcode = (map["data"] ?? "").toString();
|
||||
} else if (event is String) {
|
||||
barcode = event;
|
||||
}
|
||||
|
||||
if (barcode.isEmpty) return;
|
||||
|
||||
if (!OneContext.hasContext) return;
|
||||
|
||||
await OneContext().navigator.push(
|
||||
PageRouteBuilder(
|
||||
pageBuilder: (context, _, _) => IntentBarcodeController(
|
||||
BarcodeScanHandler(),
|
||||
initialBarcode: barcode,
|
||||
),
|
||||
opaque: false,
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
_processing = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Launch a barcode scanner with a particular context and handler.
|
||||
*
|
||||
@@ -93,6 +141,8 @@ Future<Object?> scanBarcode(
|
||||
as int;
|
||||
|
||||
switch (barcodeControllerType) {
|
||||
case BARCODE_CONTROLLER_INTENT:
|
||||
controller = IntentBarcodeController(handler);
|
||||
case BARCODE_CONTROLLER_WEDGE:
|
||||
controller = WedgeBarcodeController(handler);
|
||||
case BARCODE_CONTROLLER_CAMERA:
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
|
||||
import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/barcode/controller.dart";
|
||||
import "package:inventree/barcode/handler.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/widget/progress.dart";
|
||||
|
||||
bool intentScannerActive = false;
|
||||
|
||||
final Stream<dynamic> datawedgeStream = const EventChannel(
|
||||
"inventree/datawedge_scans",
|
||||
).receiveBroadcastStream();
|
||||
|
||||
class IntentBarcodeController extends InvenTreeBarcodeController {
|
||||
const IntentBarcodeController(
|
||||
BarcodeHandler handler, {
|
||||
super.key,
|
||||
this.initialBarcode,
|
||||
}) : super(handler);
|
||||
|
||||
final String? initialBarcode;
|
||||
|
||||
bool get isBackgroundScan => (initialBarcode ?? "").isNotEmpty;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _IntentBarcodeControllerState();
|
||||
}
|
||||
|
||||
class _IntentBarcodeControllerState extends InvenTreeBarcodeControllerState {
|
||||
StreamSubscription<dynamic>? _sub;
|
||||
|
||||
bool canScan = true;
|
||||
bool get scanning => mounted && canScan;
|
||||
|
||||
bool get isBackgroundScan =>
|
||||
(widget as IntentBarcodeController).isBackgroundScan;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
intentScannerActive = true;
|
||||
|
||||
if (isBackgroundScan) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
handleBackgroundScan(
|
||||
(widget as IntentBarcodeController).initialBarcode ?? "",
|
||||
);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
_sub = datawedgeStream.listen((event) {
|
||||
if (!scanning) return;
|
||||
|
||||
if (event is Map) {
|
||||
final map = Map<String, dynamic>.from(event);
|
||||
final data = (map["data"] ?? "").toString();
|
||||
if (data.isNotEmpty) {
|
||||
handleBarcodeData(data);
|
||||
}
|
||||
} else if (event is String && event.isNotEmpty) {
|
||||
handleBarcodeData(event);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> handleBackgroundScan(String barcode) async {
|
||||
if (!mounted || barcode.isEmpty) return;
|
||||
|
||||
final NavigatorState navigator = Navigator.of(context);
|
||||
final ModalRoute<dynamic>? route = ModalRoute.of(context);
|
||||
|
||||
showLoadingOverlay();
|
||||
|
||||
try {
|
||||
await widget.handler.processBarcode(barcode);
|
||||
} finally {
|
||||
hideLoadingOverlay();
|
||||
|
||||
if (route != null && route.isActive) {
|
||||
navigator.removeRoute(route);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
intentScannerActive = false;
|
||||
_sub?.cancel();
|
||||
_sub = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> pauseScan() async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
canScan = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> resumeScan() async {
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
canScan = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (isBackgroundScan) {
|
||||
return const AbsorbPointer(child: SizedBox.expand());
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(L10().scanBarcode)),
|
||||
backgroundColor: Colors.black.withValues(alpha: 0.9),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(flex: 5),
|
||||
const Icon(TablerIcons.barcode, size: 64),
|
||||
const Spacer(flex: 5),
|
||||
SizedBox(
|
||||
width: 64,
|
||||
height: 64,
|
||||
child: CircularProgressIndicator(
|
||||
color: scanning ? COLOR_ACTION : COLOR_PROGRESS,
|
||||
),
|
||||
),
|
||||
const Spacer(flex: 5),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Text(
|
||||
widget.handler.getOverlayText(context),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:flutter/services.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
const MethodChannel _channel = MethodChannel("inventree/wedge");
|
||||
|
||||
class WedgeDetectionResult {
|
||||
const WedgeDetectionResult({this.vendor = "none", this.supported = false});
|
||||
|
||||
final String vendor;
|
||||
|
||||
final bool supported;
|
||||
}
|
||||
|
||||
Future<WedgeDetectionResult> detectWedgeScanner() async {
|
||||
if (kIsWeb || !Platform.isAndroid) {
|
||||
return const WedgeDetectionResult();
|
||||
}
|
||||
|
||||
try {
|
||||
final Map<String, dynamic>? result = await _channel
|
||||
.invokeMapMethod<String, dynamic>("detectWedge");
|
||||
|
||||
if (result == null) return const WedgeDetectionResult();
|
||||
|
||||
return WedgeDetectionResult(
|
||||
vendor: (result["vendor"] ?? "none").toString(),
|
||||
supported: result["supported"] == true,
|
||||
);
|
||||
} catch (error) {
|
||||
return const WedgeDetectionResult();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> applyWedgeDetection(WedgeDetectionResult result) async {
|
||||
if (result.supported) {
|
||||
await InvenTreeSettingsManager().setValue(
|
||||
INV_BARCODE_SCAN_TYPE,
|
||||
BARCODE_CONTROLLER_INTENT,
|
||||
);
|
||||
}
|
||||
|
||||
await InvenTreeSettingsManager().setValue(INV_BARCODE_WEDGE_DETECTED, true);
|
||||
}
|
||||
|
||||
Future<void> initWedgeScannerDefault() async {
|
||||
if (await InvenTreeSettingsManager().getBool(
|
||||
INV_BARCODE_WEDGE_DETECTED,
|
||||
false,
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final dynamic existing = await InvenTreeSettingsManager().getValue(
|
||||
INV_BARCODE_SCAN_TYPE,
|
||||
null,
|
||||
);
|
||||
|
||||
if (existing != null) {
|
||||
await InvenTreeSettingsManager().setValue(INV_BARCODE_WEDGE_DETECTED, true);
|
||||
return;
|
||||
}
|
||||
|
||||
await applyWedgeDetection(await detectWedgeScanner());
|
||||
}
|
||||
Reference in New Issue
Block a user