2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-27 21:16:48 +00:00

Update Requirements (#541)
Some checks failed
Android / build (push) Has been cancelled
CI / test (push) Has been cancelled
iOS / build (push) Has been cancelled

* Update package requiremenst

* github workflow updates

* ios build updates

* Theme adjustments

* Further updates

* Fix typo

* Deprecated imperative apply of Flutter's Gradle plugins

Ref: https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply

* Refactor wedge scanner

* Add context checks

* Adjust behaviour if testing

* Further refactoring

* Moar checks

* Logic fix

* Fix for wedge scanner test

* Fix for barcode processing

* Fix

* Yet another fix
This commit is contained in:
Oliver 2024-10-01 12:25:11 +10:00 committed by GitHub
parent 29948e5809
commit d990508237
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
28 changed files with 519 additions and 582 deletions

View File

@ -23,7 +23,7 @@ jobs:
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.13.0' flutter-version: '3.24.3'
channel: 'stable' channel: 'stable'
- run: flutter --version - run: flutter --version
- name: Setup Gradle - name: Setup Gradle

View File

@ -36,7 +36,7 @@ jobs:
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.13.0' flutter-version: '3.24.3'
- name: Collect Translation Files - name: Collect Translation Files
run: | run: |
cd lib/l10n cd lib/l10n

View File

@ -25,7 +25,7 @@ jobs:
- name: Setup Flutter - name: Setup Flutter
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
flutter-version: '3.13.0' flutter-version: '3.24.3'
channel: 'stable' channel: 'stable'
- name: Collect Translation Files - name: Collect Translation Files
run: | run: |

View File

@ -1,3 +1,9 @@
plugins {
id "com.android.application"
id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin"
}
def localProperties = new Properties() def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties') def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) { if (localPropertiesFile.exists()) {
@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) {
} }
} }
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new FileNotFoundException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode') def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) { if (flutterVersionCode == null) {
flutterVersionCode = '1' flutterVersionCode = '1'
@ -21,10 +22,6 @@ if (flutterVersionName == null) {
flutterVersionName = '1.0' flutterVersionName = '1.0'
} }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties() def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties') def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) { if (keystorePropertiesFile.exists()) {
@ -85,5 +82,4 @@ dependencies {
androidTestImplementation 'com.android.support:multidex:2.0.1' androidTestImplementation 'com.android.support:multidex:2.0.1'
implementation "androidx.core:core:1.9.0" implementation "androidx.core:core:1.9.0"
implementation 'androidx.appcompat:appcompat:1.6.0' implementation 'androidx.appcompat:appcompat:1.6.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
} }

View File

@ -1,17 +1,3 @@
buildscript {
ext.kotlin_version = '1.8.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects { allprojects {
repositories { repositories {

View File

@ -1,15 +1,25 @@
include ':app' pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
def plugins = new Properties() repositories {
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') google()
if (pluginsFile.exists()) { mavenCentral()
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } gradlePluginPortal()
}
} }
plugins.each { name, path -> plugins {
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() id "dev.flutter.flutter-plugin-loader" version "1.0.0"
include ":$name" id "com.android.application" version "7.4.1" apply false
project(":$name").projectDir = pluginDirectory id "org.jetbrains.kotlin.android" version "1.8.10" apply false
} }
include ":app"

View File

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>11.0</string> <string>12.0</string>
</dict> </dict>
</plist> </plist>

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your projects # Uncomment this line to define a global platform for your projects
platform :ios, '11.0' platform :ios, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -39,7 +39,7 @@ post_install do |installer|
installer.pods_project.targets.each do |target| installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target) flutter_additional_ios_build_settings(target)
target.build_configurations.each do |config| target.build_configurations.each do |config|
config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '11.0' config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0'
end end
end end
end end

View File

@ -92,6 +92,7 @@
3B8B22940C363C2F0DDB698A /* Frameworks */, 3B8B22940C363C2F0DDB698A /* Frameworks */,
); );
sourceTree = "<group>"; sourceTree = "<group>";
tabWidth = 5;
}; };
97C146EF1CF9000F007C117D /* Products */ = { 97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
@ -167,7 +168,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
BuildIndependentTargetsInParallel = YES; BuildIndependentTargetsInParallel = YES;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = "The Chromium Authors"; ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -271,11 +272,9 @@
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework", "${BUILT_PRODUCTS_DIR}/DKImagePickerController/DKImagePickerController.framework",
"${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework", "${BUILT_PRODUCTS_DIR}/DKPhotoGallery/DKPhotoGallery.framework",
"${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework",
"${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework", "${BUILT_PRODUCTS_DIR}/MTBBarcodeScanner/MTBBarcodeScanner.framework",
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework", "${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework", "${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
"${BUILT_PRODUCTS_DIR}/SentryPrivate/SentryPrivate.framework",
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
"${BUILT_PRODUCTS_DIR}/audioplayers_darwin/audioplayers_darwin.framework", "${BUILT_PRODUCTS_DIR}/audioplayers_darwin/audioplayers_darwin.framework",
"${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework", "${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework",
@ -295,11 +294,9 @@
outputPaths = ( outputPaths = (
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKImagePickerController.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DKPhotoGallery.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MTBBarcodeScanner.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SentryPrivate.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers_darwin.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers_darwin.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework",

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1430" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View File

@ -1393,7 +1393,7 @@ class InvenTreeAPI {
// Find the current locale code for the running app // Find the current locale code for the running app
String get currentLocale { String get currentLocale {
if (OneContext.hasContext) { if (hasContext()) {
// Try to get app context // Try to get app context
BuildContext? context = OneContext().context; BuildContext? context = OneContext().context;

View File

@ -1,22 +1,29 @@
import "package:adaptive_theme/adaptive_theme.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:inventree/helpers.dart";
import "package:one_context/one_context.dart"; import "package:one_context/one_context.dart";
const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1); bool isDarkMode() {
// Return an "action" color based on the current theme if (!hasContext()) {
Color get COLOR_ACTION { return false;
// 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) {
return Theme.of(context).indicatorColor; return false;
}
return AdaptiveTheme.of(context).brightness == Brightness.dark;
}
// Return an "action" color based on the current theme
Color get COLOR_ACTION {
if (isDarkMode()) {
return Colors.lightBlueAccent;
} else { } else {
return Colors.lightBlue; return Colors.blue;
} }
} }
@ -24,3 +31,4 @@ const Color COLOR_WARNING = Color.fromRGBO(250, 150, 50, 1);
const Color COLOR_DANGER = Color.fromRGBO(200, 50, 75, 1); const Color COLOR_DANGER = Color.fromRGBO(200, 50, 75, 1);
const Color COLOR_SUCCESS = Color.fromRGBO(100, 200, 75, 1); const Color COLOR_SUCCESS = Color.fromRGBO(100, 200, 75, 1);
const Color COLOR_PROGRESS = Color.fromRGBO(50, 100, 200, 1); const Color COLOR_PROGRESS = Color.fromRGBO(50, 100, 200, 1);
const Color COLOR_GRAY_LIGHT = Color.fromRGBO(150, 150, 150, 1);

View File

@ -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:flutter_tabler_icons/flutter_tabler_icons.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/inventree/sales_order.dart";
import "package:inventree/preferences.dart"; import "package:inventree/preferences.dart";
import "package:inventree/widget/order/sales_order_detail.dart"; import "package:inventree/widget/order/sales_order_detail.dart";
@ -46,17 +47,20 @@ Future<void> barcodeFailure(String msg, dynamic extra) async {
msg, msg,
success: false, success: false,
onAction: () { onAction: () {
OneContext().showDialog( if (hasContext()) {
builder: (BuildContext context) => SimpleDialog( OneContext().showDialog(
title: Text(L10().barcodeError), builder: (BuildContext context) =>
children: <Widget>[ SimpleDialog(
ListTile( title: Text(L10().barcodeError),
title: Text(L10().responseData), children: <Widget>[
subtitle: Text(extra.toString()) ListTile(
) title: Text(L10().responseData),
] subtitle: Text(extra.toString())
) )
); ]
)
);
}
} }
); );
} }
@ -277,17 +281,20 @@ class BarcodeScanHandler extends BarcodeHandler {
success: false, success: false,
onAction: () { onAction: () {
OneContext().showDialog( if (hasContext()) {
builder: (BuildContext context) => SimpleDialog( OneContext().showDialog(
title: Text(L10().unknownResponse), builder: (BuildContext context) =>
children: <Widget>[ SimpleDialog(
ListTile( title: Text(L10().unknownResponse),
title: Text(L10().responseData), children: <Widget>[
subtitle: Text(data.toString()), ListTile(
) title: Text(L10().responseData),
], subtitle: Text(data.toString()),
) )
); ],
)
);
}
} }
); );
} }

View File

@ -1,4 +1,5 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:inventree/helpers.dart";
import "package:one_context/one_context.dart"; import "package:one_context/one_context.dart";
import "package:inventree/preferences.dart"; import "package:inventree/preferences.dart";
@ -43,7 +44,7 @@ class InvenTreeBarcodeControllerState extends State<InvenTreeBarcodeController>
* Barcode data should be passed as a string * Barcode data should be passed as a string
*/ */
Future<void> handleBarcodeData(String? data) async { Future<void> handleBarcodeData(String? data) async {
// Check that the data is valid, and this view is still mounted // Check that the data is valid, and this view is still mounted
if (!mounted || data == null || data.isEmpty) { if (!mounted || data == null || data.isEmpty) {
return; return;
@ -58,7 +59,11 @@ class InvenTreeBarcodeControllerState extends State<InvenTreeBarcodeController>
processingBarcode = true; processingBarcode = true;
}); });
BuildContext? context = OneContext.hasContext ? OneContext().context : null; BuildContext? context;
if (hasContext()) {
context = OneContext.hasContext ? OneContext().context : null;
}
showLoadingOverlay(context); showLoadingOverlay(context);
await pauseScan(); await pauseScan();

View File

@ -1,175 +0,0 @@
/*
* 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();
}
}

View File

@ -42,7 +42,7 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler {
final bool result = await onLocationScanned(_loc); final bool result = await onLocationScanned(_loc);
if (result && OneContext.hasContext) { if (result && hasContext()) {
OneContext().pop(); OneContext().pop();
} }
return; return;

View File

@ -1,11 +1,12 @@
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:flutter/services.dart";
import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart";
import "package:inventree/app_colors.dart"; import "package:inventree/app_colors.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/flutter_barcode_listener.dart";
import "package:inventree/l10.dart"; import "package:inventree/l10.dart";
import "package:inventree/helpers.dart"; import "package:inventree/helpers.dart";
@ -31,6 +32,12 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
bool get scanning => mounted && canScan; bool get scanning => mounted && canScan;
final FocusNode _focusNode = FocusNode();
List<String> _scannedCharacters = [];
DateTime? _lastScanTime;
@override @override
Future<void> pauseScan() async { Future<void> pauseScan() async {
@ -51,6 +58,45 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
} }
} }
// Callback for a single key press / scan
void handleKeyEvent(KeyEvent event) {
if (!scanning) {
return;
}
// Look only for key-down events
if (event is! KeyDownEvent) {
return;
}
// Ignore events without a character code
if (event.character == null) {
return;
}
DateTime now = DateTime.now();
// Throw away old characters
if (_lastScanTime == null || _lastScanTime!.isBefore(now.subtract(Duration(milliseconds: 250)))) {
_scannedCharacters.clear();
}
_lastScanTime = now;
if (event.character == "\n") {
if (_scannedCharacters.isNotEmpty) {
// Debug output required for unit testing
debug("scanned: ${_scannedCharacters.join()}");
handleBarcodeData(_scannedCharacters.join());
}
_scannedCharacters.clear();
} else {
_scannedCharacters.add(event.character!);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -66,8 +112,9 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
Spacer(flex: 5), Spacer(flex: 5),
Icon(TablerIcons.barcode, size: 64), Icon(TablerIcons.barcode, size: 64),
Spacer(flex: 5), Spacer(flex: 5),
BarcodeKeyboardListener( KeyboardListener(
useKeyDownEvent: true, autofocus: true,
focusNode: _focusNode,
child: SizedBox( child: SizedBox(
child: CircularProgressIndicator( child: CircularProgressIndicator(
color: scanning ? COLOR_ACTION : COLOR_PROGRESS color: scanning ? COLOR_ACTION : COLOR_PROGRESS
@ -75,13 +122,16 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState {
width: 64, width: 64,
height: 64, height: 64,
), ),
onBarcodeScanned: (String barcode) { onKeyEvent: (event) {
debug("scanned: ${barcode}"); handleKeyEvent(event);
if (scanning) {
// Process the barcode data
handleBarcodeData(barcode);
}
}, },
// onBarcodeScanned: (String barcode) {
// debug("scanned: ${barcode}");
// if (scanning) {
// // Process the barcode data
// handleBarcodeData(barcode);
// }
// },
), ),
Spacer(flex: 5), Spacer(flex: 5),
Padding( Padding(

View File

@ -39,13 +39,32 @@ bool debugContains(String msg, {bool raiseAssert = true}) {
} }
} }
if (!result) {
print("Debug does not contain expected string: '${msg}'");
}
if (raiseAssert) { if (raiseAssert) {
assert(result); assert(result);
} }
return result; return result;
} }
bool isTesting() {
return Platform.environment.containsKey("FLUTTER_TEST");
}
bool hasContext() {
try {
return !isTesting() && OneContext.hasContext;
} catch (error) {
return false;
}
}
/* /*
* Display a debug message if we are in testing mode, or running in debug mode * Display a debug message if we are in testing mode, or running in debug mode
*/ */
@ -83,7 +102,7 @@ Future<void> playAudioFile(String path) async {
// Debug message for unit testing // Debug message for unit testing
debug("Playing audio file: '${path}'"); debug("Playing audio file: '${path}'");
if (!OneContext.hasContext) { if (!hasContext()) {
return; return;
} }
@ -117,21 +136,13 @@ String renderCurrency(double? amount, String currency, {int decimals = 2}) {
if (currency.isEmpty) return "-"; if (currency.isEmpty) return "-";
CurrencyFormatterSettings backupSettings = CurrencyFormatterSettings( CurrencyFormat fmt = CurrencyFormat.fromCode(currency.toLowerCase()) ?? CurrencyFormat.usd;
symbol: "\$",
symbolSide: SymbolSide.left,
);
String value = CurrencyFormatter.format( String value = CurrencyFormatter.format(
amount, amount,
CurrencyFormatter.majors[currency.toLowerCase()] ?? backupSettings fmt
); );
// If we were not able to determine the currency
if (!CurrencyFormatter.majors.containsKey(currency.toLowerCase())) {
value += " ${currency}";
}
return value; return value;
} }

View File

@ -1,6 +1,7 @@
import "dart:io"; import "dart:io";
import "package:device_info_plus/device_info_plus.dart"; import "package:device_info_plus/device_info_plus.dart";
import "package:inventree/helpers.dart";
import "package:one_context/one_context.dart"; import "package:one_context/one_context.dart";
import "package:package_info_plus/package_info_plus.dart"; import "package:package_info_plus/package_info_plus.dart";
import "package:sentry_flutter/sentry_flutter.dart"; import "package:sentry_flutter/sentry_flutter.dart";
@ -129,16 +130,16 @@ Future<bool> sentryReportMessage(String message, {Map<String, String>? context})
} }
Sentry.configureScope((scope) { Sentry.configureScope((scope) {
scope.setExtra("server", server_info); scope.setContexts("server", server_info);
scope.setExtra("app", app_info); scope.setContexts("app", app_info);
scope.setExtra("device", device_info); scope.setContexts("device", device_info);
if (context != null) { if (context != null) {
scope.setExtra("context", context); scope.setContexts("context", context);
} }
// Catch stacktrace data if possible // Catch stacktrace data if possible
scope.setExtra("stacktrace", StackTrace.current.toString()); scope.setContexts("stacktrace", StackTrace.current.toString());
}); });
try { try {
@ -203,7 +204,7 @@ Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTr
// Ensure we pass the 'source' of the error // Ensure we pass the 'source' of the error
context["source"] = source; context["source"] = source;
if (OneContext.hasContext) { if (hasContext()) {
final ctx = OneContext().context; final ctx = OneContext().context;
if (ctx != null) { if (ctx != null) {
@ -213,10 +214,10 @@ Future<void> sentryReportError(String source, dynamic error, StackTrace? stackTr
} }
Sentry.configureScope((scope) { Sentry.configureScope((scope) {
scope.setExtra("server", server_info); scope.setContexts("server", server_info);
scope.setExtra("app", app_info); scope.setContexts("app", app_info);
scope.setExtra("device", device_info); scope.setContexts("device", device_info);
scope.setExtra("context", context); scope.setContexts("context", context);
}); });
Sentry.captureException(error, stackTrace: stackTrace).catchError((error) { Sentry.captureException(error, stackTrace: stackTrace).catchError((error) {

View File

@ -4,18 +4,23 @@ import "package:flutter_gen/gen_l10n/app_localizations_en.dart";
import "package:one_context/one_context.dart"; import "package:one_context/one_context.dart";
import "package:flutter/material.dart"; import "package:flutter/material.dart";
import "package:inventree/helpers.dart";
// Shortcut function to reduce boilerplate! // Shortcut function to reduce boilerplate!
I18N L10() I18N L10()
{ {
if (OneContext.hasContext) { // Testing mode - ignore context
BuildContext? _ctx = OneContext().context; if (!hasContext()) {
return I18NEn();
}
if (_ctx != null) { BuildContext? _ctx = OneContext().context;
I18N? i18n = I18N.of(_ctx);
if (i18n != null) { if (_ctx != null) {
return i18n; I18N? i18n = I18N.of(_ctx);
}
if (i18n != null) {
return i18n;
} }
} }

View File

@ -162,13 +162,13 @@ class InvenTreeAppState extends State<StatefulWidget> {
return AdaptiveTheme( return AdaptiveTheme(
light: ThemeData( light: ThemeData(
brightness: Brightness.light, brightness: Brightness.light,
primarySwatch: Colors.lightBlue, colorSchemeSeed: Colors.lightBlueAccent,
secondaryHeaderColor: Colors.blueGrey useMaterial3: true,
), ),
dark: ThemeData( dark: ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
primarySwatch: Colors.lightBlue, colorSchemeSeed: Colors.blue,
secondaryHeaderColor: Colors.blueGrey, useMaterial3: true,
), ),
initial: savedThemeMode ?? AdaptiveThemeMode.light, initial: savedThemeMode ?? AdaptiveThemeMode.light,
builder: (light, dark) => MaterialApp( builder: (light, dark) => MaterialApp(

View File

@ -3,7 +3,6 @@ import "dart:ui";
import "package:inventree/l10n/supported_locales.dart"; import "package:inventree/l10n/supported_locales.dart";
import "package:path_provider/path_provider.dart"; import "package:path_provider/path_provider.dart";
import "package:sembast/sembast.dart";
import "package:sembast/sembast_io.dart"; import "package:sembast/sembast_io.dart";
import "package:path/path.dart"; import "package:path/path.dart";

View File

@ -31,6 +31,10 @@ Future<void> choiceDialog(String title, List<Widget> items, {Function? onSelecte
); );
} }
if (!hasContext()) {
return;
}
OneContext().showDialog( OneContext().showDialog(
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
@ -63,6 +67,10 @@ Future<void> confirmationDialog(String title, String text, {Color? color, IconDa
String _accept = acceptText ?? L10().ok; String _accept = acceptText ?? L10().ok;
String _reject = rejectText ?? L10().cancel; String _reject = rejectText ?? L10().cancel;
if (!hasContext()) {
return;
}
OneContext().showDialog( OneContext().showDialog(
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
@ -176,6 +184,10 @@ Future<void> showErrorDialog(String title, {String description = "", APIResponse
} }
} }
if (!hasContext()) {
return;
}
OneContext().showDialog( OneContext().showDialog(
builder: (context) => SimpleDialog( builder: (context) => SimpleDialog(
title: ListTile( title: ListTile(
@ -196,7 +208,7 @@ Future<void> showErrorDialog(String title, {String description = "", APIResponse
*/ */
Future<void> showServerError(String url, String title, String description) async { Future<void> showServerError(String url, String title, String description) async {
if (!OneContext.hasContext) { if (!hasContext()) {
return; return;
} }

View File

@ -13,7 +13,7 @@ void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? suc
debug("showSnackIcon: '${text}'"); debug("showSnackIcon: '${text}'");
// Escape quickly if we do not have context // Escape quickly if we do not have context
if (!OneContext.hasContext) { if (!hasContext()) {
// Debug message for unit testing // Debug message for unit testing
return; return;
} }

View File

@ -301,12 +301,16 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
// Serial number field is not required here // Serial number field is not required here
fields.remove("serial"); fields.remove("serial");
Map<String, dynamic> data = {};
if (location != null) {
data["location"] = location!.pk;
}
InvenTreeStockItem().createForm( InvenTreeStockItem().createForm(
context, context,
L10().stockItemCreate, L10().stockItemCreate,
data: { data: data,
"location": location != null ? location!.pk : null,
},
fields: fields, fields: fields,
onSuccess: (result) async { onSuccess: (result) async {
Map<String, dynamic> data = result as Map<String, dynamic>; Map<String, dynamic> data = result as Map<String, dynamic>;

File diff suppressed because it is too large Load Diff

View File

@ -8,15 +8,15 @@ environment:
dependencies: dependencies:
adaptive_theme: ^3.3.0 # Theme management (e.g. dark mode) adaptive_theme: ^3.3.0 # Theme management (e.g. dark mode)
audioplayers: ^4.1.0 # Play audio files audioplayers: ^6.1.0 # Play audio files
cached_network_image: ^3.3.1 # Download and cache remote images cached_network_image: ^3.3.1 # Download and cache remote images
camera: ^0.10.3 # Camera camera: ^0.10.3 # Camera
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
currency_formatter: ^2.0.1 currency_formatter: ^2.2.1 # Currency formatting
datetime_picker_formfield: ^2.0.1 # Date / time picker datetime_picker_formfield: ^2.0.1 # Date / time picker
device_info_plus: ^8.2.2 # Information about the device device_info_plus: ^10.1.2 # Information about the device
dropdown_search: ^5.0.6 # Dropdown autocomplete form fields dropdown_search: ^5.0.6 # Dropdown autocomplete form fields
file_picker: ^5.3.1 # Select files from the device file_picker: ^8.1.2 # Select files from the device
flutter: flutter:
sdk: flutter sdk: flutter
flutter_cache_manager: ^3.3.0 flutter_cache_manager: ^3.3.0
@ -27,19 +27,19 @@ dependencies:
flutter_overlay_loader: ^2.0.0 # Overlay screen support flutter_overlay_loader: ^2.0.0 # Overlay screen support
flutter_speed_dial: ^6.2.0 # Speed dial / FAB implementation flutter_speed_dial: ^6.2.0 # Speed dial / FAB implementation
flutter_tabler_icons: ^1.35.0 flutter_tabler_icons: ^1.35.0
http: ^0.13.6 http: ^1.2.2
image_picker: ^1.0.8 # Select or take photos image_picker: ^1.1.2 # Select or take photos
infinite_scroll_pagination: ^4.0.0 # Let the server do all the work! infinite_scroll_pagination: ^4.0.0 # Let the server do all the work!
intl: ^0.18.0 intl: ^0.19.0
one_context: ^2.1.0 # Dialogs without requiring context one_context: ^4.0.0 # Dialogs without requiring context
open_filex: ^4.4.0 # Open local files open_filex: ^4.5.0 # Open local files
package_info_plus: ^3.0.2 # App information introspection package_info_plus: ^8.0.2 # App information introspection
path: ^1.8.2 path: ^1.9.0
path_provider: ^2.1.3 # Local file storage path_provider: ^2.1.3 # Local file storage
qr_code_scanner: ^1.0.1 # Barcode scanning qr_code_scanner: ^1.0.1 # Barcode scanning
sembast: ^3.6.0 # NoSQL data storage sembast: ^3.6.0 # NoSQL data storage
sentry_flutter: 7.9.0 # Error reporting sentry_flutter: 8.9.0 # Error reporting
url_launcher: ^6.2.4 # Open link in system browser url_launcher: ^6.3.0 # Open link in system browser
dev_dependencies: dev_dependencies:
flutter_launcher_icons: ^0.11.0 flutter_launcher_icons: ^0.11.0

View File

@ -21,7 +21,7 @@ void main() {
await simulateKeyDownEvent(LogicalKeyboardKey.keyA); await simulateKeyDownEvent(LogicalKeyboardKey.keyA);
await simulateKeyDownEvent(LogicalKeyboardKey.keyB); await simulateKeyDownEvent(LogicalKeyboardKey.keyB);
await simulateKeyDownEvent(LogicalKeyboardKey.keyC); await simulateKeyDownEvent(LogicalKeyboardKey.keyC);
await simulateKeyDownEvent(LogicalKeyboardKey.enter); await simulateKeyDownEvent(LogicalKeyboardKey.enter, character: "\n");
// Check debug output // Check debug output
debugContains("scanned: abc"); debugContains("scanned: abc");