mirror of
https://github.com/inventree/inventree-app.git
synced 2025-06-17 20:55:26 +00:00
Merge pull request #135 from inventree/unit-testing
Unit testing
(cherry picked from commit d55f594342
)
This commit is contained in:
33
lib/api.dart
33
lib/api.dart
@ -6,7 +6,7 @@ import "package:flutter/foundation.dart";
|
||||
import "package:http/http.dart" as http;
|
||||
import "package:intl/intl.dart";
|
||||
import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
import "package:open_file/open_file.dart";
|
||||
import "package:cached_network_image/cached_network_image.dart";
|
||||
@ -16,6 +16,7 @@ import "package:flutter_cache_manager/flutter_cache_manager.dart";
|
||||
|
||||
import "package:inventree/widget/dialogs.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/inventree/sentry.dart";
|
||||
import "package:inventree/inventree/model.dart";
|
||||
import "package:inventree/user_profile.dart";
|
||||
@ -201,11 +202,13 @@ class InvenTreeAPI {
|
||||
// Authentication token (initially empty, must be requested)
|
||||
String _token = "";
|
||||
|
||||
bool get hasToken => _token.isNotEmpty;
|
||||
|
||||
/*
|
||||
* Check server connection and display messages if not connected.
|
||||
* Useful as a precursor check before performing operations.
|
||||
*/
|
||||
bool checkConnection(BuildContext context) {
|
||||
bool checkConnection() {
|
||||
// Firstly, is the server connected?
|
||||
if (!isConnected()) {
|
||||
|
||||
@ -278,7 +281,7 @@ class InvenTreeAPI {
|
||||
bool _connecting = false;
|
||||
|
||||
bool isConnected() {
|
||||
return profile != null && _connected && baseUrl.isNotEmpty && _token.isNotEmpty;
|
||||
return profile != null && _connected && baseUrl.isNotEmpty && hasToken;
|
||||
}
|
||||
|
||||
bool isConnecting() {
|
||||
@ -289,14 +292,10 @@ class InvenTreeAPI {
|
||||
static final InvenTreeAPI _api = InvenTreeAPI._internal();
|
||||
|
||||
// API endpoint for receiving purchase order line items was introduced in v12
|
||||
bool supportPoReceive() {
|
||||
return apiVersion >= 12;
|
||||
}
|
||||
bool get supportsPoReceive => apiVersion >= 12;
|
||||
|
||||
// "Modern" API transactions were implemented in API v14
|
||||
bool supportModernStockTransactions() {
|
||||
return apiVersion >= 14;
|
||||
}
|
||||
bool get supportsModernStockTransactions => apiVersion >= 14;
|
||||
|
||||
/*
|
||||
* Connect to the remote InvenTree server:
|
||||
@ -338,7 +337,7 @@ class InvenTreeAPI {
|
||||
// Clear the list of available plugins
|
||||
_plugins.clear();
|
||||
|
||||
print("Connecting to ${apiUrl} -> username=${username}");
|
||||
debug("Connecting to ${apiUrl} -> username=${username}");
|
||||
|
||||
APIResponse response;
|
||||
|
||||
@ -431,7 +430,7 @@ class InvenTreeAPI {
|
||||
// Return the received token
|
||||
_token = (data["token"] ?? "") as String;
|
||||
|
||||
print("Received token - $_token");
|
||||
debug("Received token from server");
|
||||
|
||||
// Request user role information (async)
|
||||
getUserRoles();
|
||||
@ -445,7 +444,7 @@ class InvenTreeAPI {
|
||||
}
|
||||
|
||||
void disconnectFromServer() {
|
||||
print("InvenTreeAPI().disconnectFromServer()");
|
||||
debug("API : disconnectFromServer()");
|
||||
|
||||
_connected = false;
|
||||
_connecting = false;
|
||||
@ -501,7 +500,7 @@ class InvenTreeAPI {
|
||||
|
||||
roles.clear();
|
||||
|
||||
print("Requesting user role data");
|
||||
debug("API: Requesting user role data");
|
||||
|
||||
// Next we request the permissions assigned to the current user
|
||||
// Note: 2021-02-27 this "roles" feature for the API was just introduced.
|
||||
@ -531,7 +530,7 @@ class InvenTreeAPI {
|
||||
return;
|
||||
}
|
||||
|
||||
print("Requesting plugin information");
|
||||
debug("API: getPluginInformation()");
|
||||
|
||||
// Request a list of plugins from the server
|
||||
final List<InvenTreeModel> results = await InvenTreePlugin().list();
|
||||
@ -661,7 +660,7 @@ class InvenTreeAPI {
|
||||
_request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale());
|
||||
|
||||
} on SocketException catch (error) {
|
||||
print("SocketException at ${url}: ${error.toString()}");
|
||||
debug("SocketException at ${url}: ${error.toString()}");
|
||||
showServerError(L10().connectionRefused, error.toString());
|
||||
return;
|
||||
} on TimeoutException {
|
||||
@ -670,7 +669,7 @@ class InvenTreeAPI {
|
||||
return;
|
||||
} on HandshakeException catch (error) {
|
||||
print("HandshakeException at ${url}:");
|
||||
print(error.toString());
|
||||
debug(error.toString());
|
||||
showServerError(L10().serverCertificateError, error.toString());
|
||||
return;
|
||||
} catch (error, stackTrace) {
|
||||
@ -1233,8 +1232,6 @@ class InvenTreeAPI {
|
||||
|
||||
var plugins = getPlugins(mixin: "locate");
|
||||
|
||||
print("locateItemOrLocation");
|
||||
|
||||
if (plugins.isEmpty) {
|
||||
// TODO: Error message
|
||||
return;
|
||||
|
@ -335,7 +335,7 @@ class APIFormField {
|
||||
controller.text = hash;
|
||||
data["value"] = hash;
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeAssigned,
|
||||
|
@ -1,5 +1,3 @@
|
||||
|
||||
|
||||
import "dart:ui";
|
||||
|
||||
const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1);
|
||||
|
@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Class for managing app-level configuration options
|
||||
*/
|
||||
|
||||
import "package:sembast/sembast.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
// Settings key values
|
||||
const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed";
|
||||
const String INV_HOME_SHOW_PO = "homeShowPo";
|
||||
const String INV_HOME_SHOW_MANUFACTURERS = "homeShowManufacturers";
|
||||
const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers";
|
||||
const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers";
|
||||
|
||||
const String INV_SOUNDS_BARCODE = "barcodeSounds";
|
||||
const String INV_SOUNDS_SERVER = "serverSounds";
|
||||
|
||||
const String INV_PART_SUBCATEGORY = "partSubcategory";
|
||||
|
||||
const String INV_STOCK_SUBLOCATION = "stockSublocation";
|
||||
const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
|
||||
|
||||
const String INV_REPORT_ERRORS = "reportErrors";
|
||||
|
||||
const String INV_STRICT_HTTPS = "strictHttps";
|
||||
|
||||
class InvenTreeSettingsManager {
|
||||
|
||||
factory InvenTreeSettingsManager() {
|
||||
return _manager;
|
||||
}
|
||||
|
||||
InvenTreeSettingsManager._internal();
|
||||
|
||||
final store = StoreRef("settings");
|
||||
|
||||
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
|
||||
|
||||
Future<dynamic> getValue(String key, dynamic backup) async {
|
||||
|
||||
final value = await store.record(key).get(await _db);
|
||||
|
||||
if (value == null) {
|
||||
return backup;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Load a boolean setting
|
||||
Future<bool> getBool(String key, bool backup) async {
|
||||
final dynamic value = await getValue(key, backup);
|
||||
|
||||
if (value is bool) {
|
||||
return value;
|
||||
} else {
|
||||
return backup;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setValue(String key, dynamic value) async {
|
||||
|
||||
await store.record(key).put(await _db, value);
|
||||
}
|
||||
|
||||
// Ensure we only ever create a single instance of this class
|
||||
static final InvenTreeSettingsManager _manager = InvenTreeSettingsManager._internal();
|
||||
}
|
@ -11,15 +11,38 @@ import "package:qr_code_scanner/qr_code_scanner.dart";
|
||||
|
||||
import "package:inventree/inventree/stock.dart";
|
||||
import "package:inventree/inventree/part.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
import "package:inventree/widget/location_display.dart";
|
||||
import "package:inventree/widget/part_detail.dart";
|
||||
import "package:inventree/widget/stock_detail.dart";
|
||||
|
||||
|
||||
/*
|
||||
* Play an audible 'success' alert to the user.
|
||||
*/
|
||||
Future<void> barcodeSuccessTone() async {
|
||||
|
||||
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool;
|
||||
|
||||
if (en) {
|
||||
playAudioFile("sounds/barcode_scan.mp3");
|
||||
}
|
||||
}
|
||||
|
||||
Future <void> barcodeFailureTone() async {
|
||||
|
||||
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool;
|
||||
|
||||
if (en) {
|
||||
playAudioFile("sounds/barcode_error.mp3");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class BarcodeHandler {
|
||||
/*
|
||||
* Class which "handles" a barcode, by communicating with the InvenTree server,
|
||||
@ -44,7 +67,7 @@ class BarcodeHandler {
|
||||
// Called when the server does not know about a barcode
|
||||
// Override this function
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeNoMatch,
|
||||
@ -55,7 +78,7 @@ class BarcodeHandler {
|
||||
|
||||
Future<void> onBarcodeUnhandled(BuildContext context, Map<String, dynamic> data) async {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
// Called when the server returns an unhandled response
|
||||
showServerError(L10().responseUnknown, data.toString());
|
||||
@ -125,7 +148,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
@override
|
||||
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeNoMatch,
|
||||
@ -146,7 +169,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
|
||||
if (pk > 0) {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
InvenTreeStockLocation().get(pk).then((var loc) {
|
||||
if (loc is InvenTreeStockLocation) {
|
||||
@ -156,7 +179,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
});
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().invalidStockLocation,
|
||||
@ -170,7 +193,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
|
||||
if (pk > 0) {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
InvenTreeStockItem().get(pk).then((var item) {
|
||||
|
||||
@ -183,7 +206,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
});
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().invalidStockItem,
|
||||
@ -196,7 +219,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
|
||||
if (pk > 0) {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
InvenTreePart().get(pk).then((var part) {
|
||||
|
||||
@ -209,7 +232,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
});
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().invalidPart,
|
||||
@ -218,7 +241,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
}
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeUnknown,
|
||||
@ -275,7 +298,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
||||
|
||||
if (result) {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
Navigator.of(context).pop();
|
||||
|
||||
@ -285,7 +308,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
||||
);
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeScanIntoLocationFailure,
|
||||
@ -294,7 +317,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
||||
}
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().invalidStockLocation,
|
||||
@ -329,14 +352,14 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
||||
|
||||
if (item == null) {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().invalidStockItem,
|
||||
success: false,
|
||||
);
|
||||
} else if (item.locationId == location.pk) {
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().itemInLocation,
|
||||
@ -347,7 +370,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
||||
|
||||
if (result) {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeScanIntoLocationSuccess,
|
||||
@ -355,7 +378,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
||||
);
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeScanIntoLocationFailure,
|
||||
@ -365,7 +388,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
||||
}
|
||||
} else {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
// Does not match a valid stock item!
|
||||
showSnackIcon(
|
||||
@ -401,7 +424,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
||||
@override
|
||||
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
|
||||
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
// If the barcode is known, we can"t assign it to the stock item!
|
||||
showSnackIcon(
|
||||
@ -424,7 +447,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
||||
String hash = (data["hash"] ?? "") as String;
|
||||
|
||||
if (hash.isEmpty) {
|
||||
failureTone();
|
||||
barcodeFailureTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeError,
|
||||
@ -432,7 +455,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
||||
);
|
||||
} else {
|
||||
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
// Close the barcode scanner
|
||||
Navigator.of(context).pop();
|
||||
|
@ -7,8 +7,22 @@
|
||||
* supressing trailing zeroes
|
||||
*/
|
||||
|
||||
import "dart:io";
|
||||
|
||||
import "package:audioplayers/audioplayers.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
|
||||
|
||||
/*
|
||||
* Display a debug message if we are in testing mode, or running in debug mode
|
||||
*/
|
||||
void debug(dynamic msg) {
|
||||
|
||||
if (Platform.environment.containsKey("FLUTTER_TEST")) {
|
||||
print("DEBUG: ${msg.toString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
String simpleNumberString(double number) {
|
||||
// Ref: https://stackoverflow.com/questions/55152175/how-to-remove-trailing-zeros-using-dart
|
||||
@ -16,22 +30,19 @@ String simpleNumberString(double number) {
|
||||
return number.toStringAsFixed(number.truncateToDouble() == number ? 0 : 1);
|
||||
}
|
||||
|
||||
Future<void> successTone() async {
|
||||
/*
|
||||
* Play an audio file from the requested path.
|
||||
*
|
||||
* Note: If OneContext module fails the 'hasContext' check,
|
||||
* we will not attempt to play the sound
|
||||
*/
|
||||
Future<void> playAudioFile(String path) async {
|
||||
|
||||
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool;
|
||||
|
||||
if (en) {
|
||||
final player = AudioCache();
|
||||
player.play("sounds/barcode_scan.mp3");
|
||||
if (!OneContext.hasContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
final player = AudioCache();
|
||||
player.play(path);
|
||||
|
||||
}
|
||||
|
||||
Future <void> failureTone() async {
|
||||
|
||||
final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool;
|
||||
|
||||
if (en) {
|
||||
final player = AudioCache();
|
||||
player.play("sounds/barcode_error.mp3");
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import "dart:io";
|
||||
|
||||
import "package:device_info_plus/device_info_plus.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:package_info_plus/package_info_plus.dart";
|
||||
import "package:sentry_flutter/sentry_flutter.dart";
|
||||
|
||||
|
@ -533,7 +533,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
Map<String, dynamic> data = {};
|
||||
|
||||
// Note: Format of adjustment API was updated in API v14
|
||||
if (api.supportModernStockTransactions()) {
|
||||
if (api.supportsModernStockTransactions) {
|
||||
// Modern (> 14) API
|
||||
data = {
|
||||
"items": [
|
||||
@ -560,7 +560,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
}
|
||||
|
||||
// Expected API return code depends on server API version
|
||||
final int expected_response = api.supportModernStockTransactions() ? 201 : 200;
|
||||
final int expected_response = api.supportsModernStockTransactions ? 201 : 200;
|
||||
|
||||
var response = await api.post(
|
||||
endpoint,
|
||||
|
14
lib/l10.dart
14
lib/l10.dart
@ -7,16 +7,18 @@ import "package:flutter/material.dart";
|
||||
// Shortcut function to reduce boilerplate!
|
||||
I18N L10()
|
||||
{
|
||||
BuildContext? _ctx = OneContext().context;
|
||||
if (OneContext.hasContext) {
|
||||
BuildContext? _ctx = OneContext().context;
|
||||
|
||||
if (_ctx != null) {
|
||||
I18N? i18n = I18N.of(_ctx);
|
||||
if (_ctx != null) {
|
||||
I18N? i18n = I18N.of(_ctx);
|
||||
|
||||
if (i18n != null) {
|
||||
return i18n;
|
||||
if (i18n != null) {
|
||||
return i18n;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for "null" context
|
||||
return I18NEn();
|
||||
return I18NEn();
|
||||
}
|
@ -6,6 +6,26 @@ import "package:sembast/sembast_io.dart";
|
||||
import "package:path/path.dart";
|
||||
|
||||
|
||||
// Settings key values
|
||||
const String INV_HOME_SHOW_SUBSCRIBED = "homeShowSubscribed";
|
||||
const String INV_HOME_SHOW_PO = "homeShowPo";
|
||||
const String INV_HOME_SHOW_MANUFACTURERS = "homeShowManufacturers";
|
||||
const String INV_HOME_SHOW_CUSTOMERS = "homeShowCustomers";
|
||||
const String INV_HOME_SHOW_SUPPLIERS = "homeShowSuppliers";
|
||||
|
||||
const String INV_SOUNDS_BARCODE = "barcodeSounds";
|
||||
const String INV_SOUNDS_SERVER = "serverSounds";
|
||||
|
||||
const String INV_PART_SUBCATEGORY = "partSubcategory";
|
||||
|
||||
const String INV_STOCK_SUBLOCATION = "stockSublocation";
|
||||
const String INV_STOCK_SHOW_HISTORY = "stockShowHistory";
|
||||
|
||||
const String INV_REPORT_ERRORS = "reportErrors";
|
||||
|
||||
const String INV_STRICT_HTTPS = "strictHttps";
|
||||
|
||||
|
||||
/*
|
||||
* Class for storing InvenTree preferences in a NoSql DB
|
||||
*/
|
||||
@ -40,46 +60,59 @@ class InvenTreePreferencesDB {
|
||||
// Get a platform-specific directory where persistent app data can be stored
|
||||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
print("Documents Dir: ${appDocumentDir.toString()}");
|
||||
|
||||
print("Path: ${appDocumentDir.path}");
|
||||
|
||||
// Path with the form: /platform-specific-directory/demo.db
|
||||
final dbPath = join(appDocumentDir.path, "InvenTreeSettings.db");
|
||||
|
||||
final database = await databaseFactoryIo.openDatabase(dbPath);
|
||||
|
||||
// Any code awaiting the Completer's future will now start executing
|
||||
_dbOpenCompleter.complete(database);
|
||||
}
|
||||
}
|
||||
|
||||
class InvenTreePreferences {
|
||||
|
||||
factory InvenTreePreferences() {
|
||||
return _api;
|
||||
/*
|
||||
* InvenTree setings manager class.
|
||||
* Provides functions for loading and saving settings, with provision for default values
|
||||
*/
|
||||
class InvenTreeSettingsManager {
|
||||
|
||||
factory InvenTreeSettingsManager() {
|
||||
return _manager;
|
||||
}
|
||||
|
||||
InvenTreePreferences._internal();
|
||||
InvenTreeSettingsManager._internal();
|
||||
|
||||
/* The following settings are not stored to persistent storage,
|
||||
* instead they are only used as "session preferences".
|
||||
* They are kept here as a convenience only.
|
||||
*/
|
||||
final store = StoreRef("settings");
|
||||
|
||||
// Expand subcategory list in PartCategory view
|
||||
bool expandCategoryList = false;
|
||||
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
|
||||
|
||||
// Expand part list in PartCategory view
|
||||
bool expandPartList = true;
|
||||
Future<dynamic> getValue(String key, dynamic backup) async {
|
||||
|
||||
// Expand sublocation list in StockLocation view
|
||||
bool expandLocationList = false;
|
||||
final value = await store.record(key).get(await _db);
|
||||
|
||||
// Expand item list in StockLocation view
|
||||
bool expandStockList = true;
|
||||
if (value == null) {
|
||||
return backup;
|
||||
}
|
||||
|
||||
// Ensure we only ever create a single instance of the preferences class
|
||||
static final InvenTreePreferences _api = InvenTreePreferences._internal();
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
// Load a boolean setting
|
||||
Future<bool> getBool(String key, bool backup) async {
|
||||
final dynamic value = await getValue(key, backup);
|
||||
|
||||
if (value is bool) {
|
||||
return value;
|
||||
} else {
|
||||
return backup;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setValue(String key, dynamic value) async {
|
||||
|
||||
await store.record(key).put(await _db, value);
|
||||
}
|
||||
|
||||
// Ensure we only ever create a single instance of this class
|
||||
static final InvenTreeSettingsManager _manager = InvenTreeSettingsManager._internal();
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import "package:flutter/material.dart";
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
|
||||
class InvenTreeAppSettingsWidget extends StatefulWidget {
|
||||
|
@ -5,7 +5,7 @@ import "package:inventree/l10.dart";
|
||||
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
class HomeScreenSettingsWidget extends StatefulWidget {
|
||||
@override
|
||||
|
@ -344,7 +344,6 @@ class _ProfileEditState extends State<ProfileEditWidget> {
|
||||
Uri uri = Uri.parse(value);
|
||||
|
||||
if (uri.hasScheme) {
|
||||
print("Scheme: ${uri.scheme}");
|
||||
if (!["http", "https"].contains(uri.scheme.toLowerCase())) {
|
||||
return L10().serverStart;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
|
||||
import "package:sembast/sembast.dart";
|
||||
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
class UserProfile {
|
||||
@ -62,75 +63,94 @@ class UserProfileDBManager {
|
||||
|
||||
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
|
||||
|
||||
/*
|
||||
* Check if a profile with the specified name exists in the database
|
||||
*/
|
||||
Future<bool> profileNameExists(String name) async {
|
||||
|
||||
final finder = Finder(filter: Filter.equals("name", name));
|
||||
final profiles = await getAllProfiles();
|
||||
|
||||
final profiles = await store.find(await _db, finder: finder);
|
||||
for (var prf in profiles) {
|
||||
if (name == prf.name) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return profiles.isNotEmpty;
|
||||
// No match found!
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> addProfile(UserProfile profile) async {
|
||||
/*
|
||||
* Add a new UserProfile to the profiles database.
|
||||
*/
|
||||
Future<bool> addProfile(UserProfile profile) async {
|
||||
|
||||
if (profile.name.isEmpty || profile.username.isEmpty || profile.password.isEmpty) {
|
||||
debug("addProfile() : Profile missing required values - not adding to database");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if a profile already exists with the name
|
||||
final bool exists = await profileNameExists(profile.name);
|
||||
|
||||
if (exists) {
|
||||
print("UserProfile '${profile.name}' already exists");
|
||||
return;
|
||||
debug("addProfile() : UserProfile '${profile.name}' already exists");
|
||||
return false;
|
||||
}
|
||||
|
||||
int key = await store.add(await _db, profile.toJson()) as int;
|
||||
|
||||
print("Added user profile <${key}> - '${profile.name}'");
|
||||
|
||||
// Record the key
|
||||
profile.key = key;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> selectProfile(int key) async {
|
||||
/*
|
||||
* Mark the particular profile as selected
|
||||
*/
|
||||
/*
|
||||
* Update the selected profile in the database.
|
||||
* The unique integer <key> is used to determine if the profile already exists.
|
||||
*/
|
||||
Future<bool> updateProfile(UserProfile profile) async {
|
||||
|
||||
final result = await store.record("selected").put(await _db, key);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Future<void> updateProfile(UserProfile profile) async {
|
||||
|
||||
if (profile.key == null) {
|
||||
await addProfile(profile);
|
||||
return;
|
||||
// Prevent invalid profile data from being updated
|
||||
if (profile.name.isEmpty || profile.username.isEmpty || profile.password.isEmpty) {
|
||||
debug("updateProfile() : Profile missing required values - not updating");
|
||||
return false;
|
||||
}
|
||||
|
||||
final result = await store.record(profile.key).update(await _db, profile.toJson());
|
||||
if (profile.key == null) {
|
||||
bool result = await addProfile(profile);
|
||||
return result;
|
||||
}
|
||||
|
||||
print("Updated user profile <${profile.key}> - '${profile.name}'");
|
||||
await store.record(profile.key).update(await _db, profile.toJson());
|
||||
|
||||
return result;
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove a user profile from the database
|
||||
*/
|
||||
Future<void> deleteProfile(UserProfile profile) async {
|
||||
await store.record(profile.key).delete(await _db);
|
||||
print("Deleted user profile <${profile.key}> - '${profile.name}'");
|
||||
}
|
||||
|
||||
/*
|
||||
* Return the currently selected profile.
|
||||
* The key of the UserProfile should match the "selected" property
|
||||
*/
|
||||
Future<UserProfile?> getSelectedProfile() async {
|
||||
/*
|
||||
* Return the currently selected profile.
|
||||
*
|
||||
* key should match the "selected" property
|
||||
*/
|
||||
|
||||
final selected = await store.record("selected").get(await _db);
|
||||
|
||||
final profiles = await store.find(await _db);
|
||||
|
||||
debug("getSelectedProfile() : ${profiles.length} profiles available - selected = ${selected}");
|
||||
|
||||
for (int idx = 0; idx < profiles.length; idx++) {
|
||||
|
||||
debug("- Checking ${idx} - key = ${profiles[idx].key} - ${profiles[idx].value.toString()}");
|
||||
|
||||
if (profiles[idx].key is int && profiles[idx].key == selected) {
|
||||
return UserProfile.fromJson(
|
||||
profiles[idx].key as int,
|
||||
@ -158,14 +178,43 @@ class UserProfileDBManager {
|
||||
|
||||
if (profiles[idx].key is int) {
|
||||
profileList.add(
|
||||
UserProfile.fromJson(
|
||||
profiles[idx].key as int,
|
||||
profiles[idx].value as Map<String, dynamic>,
|
||||
profiles[idx].key == selected,
|
||||
));
|
||||
UserProfile.fromJson(
|
||||
profiles[idx].key as int,
|
||||
profiles[idx].value as Map<String, dynamic>,
|
||||
profiles[idx].key == selected,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return profileList;
|
||||
}
|
||||
|
||||
/*
|
||||
* Mark the particular profile as selected
|
||||
*/
|
||||
Future<void> selectProfile(int key) async {
|
||||
await store.record("selected").put(await _db, key);
|
||||
}
|
||||
|
||||
/*
|
||||
* Look-up and select a profile by name.
|
||||
* Return true if the profile was selected
|
||||
*/
|
||||
Future<bool> selectProfileByName(String name) async {
|
||||
var profiles = await getAllProfiles();
|
||||
|
||||
for (var prf in profiles) {
|
||||
if (prf.name == name) {
|
||||
int key = prf.key ?? -1;
|
||||
|
||||
if (key >= 0) {
|
||||
await selectProfile(key);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
import "package:audioplayers/audioplayers.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:inventree/widget/snacks.dart";
|
||||
|
||||
Future<void> confirmationDialog(String title, String text, {IconData icon = FontAwesomeIcons.questionCircle, String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async {
|
||||
|
||||
String _accept = acceptText ?? L10().ok;
|
||||
@ -108,8 +108,7 @@ Future<void> showServerError(String title, String description) async {
|
||||
final bool tones = await InvenTreeSettingsManager().getValue(INV_SOUNDS_SERVER, true) as bool;
|
||||
|
||||
if (tones) {
|
||||
final player = AudioCache();
|
||||
player.play("sounds/server_error.mp3");
|
||||
playAudioFile("sounds/server_error.mp3");
|
||||
}
|
||||
|
||||
showSnackIcon(
|
||||
|
@ -34,7 +34,7 @@ class InvenTreeDrawer extends StatelessWidget {
|
||||
|
||||
void _search() {
|
||||
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
_closeDrawer();
|
||||
|
||||
@ -51,7 +51,7 @@ class InvenTreeDrawer extends StatelessWidget {
|
||||
* Upon successful scan, data are passed off to be decoded.
|
||||
*/
|
||||
Future <void> _scan() async {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
_closeDrawer();
|
||||
scanQrCode(context);
|
||||
|
@ -6,7 +6,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/app_colors.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:inventree/barcode.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/settings/login.dart";
|
||||
@ -71,13 +71,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
||||
UserProfile? _profile;
|
||||
|
||||
void _scan(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
scanQrCode(context);
|
||||
}
|
||||
|
||||
void _showParts(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
|
||||
}
|
||||
@ -87,7 +87,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
||||
}
|
||||
|
||||
void _showStarredParts(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
@ -100,13 +100,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
||||
}
|
||||
|
||||
void _showStock(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
||||
}
|
||||
|
||||
void _showPurchaseOrders(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(
|
||||
context,
|
||||
@ -118,19 +118,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
||||
|
||||
/*
|
||||
void _showSuppliers(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"})));
|
||||
}
|
||||
|
||||
void _showManufacturers(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"})));
|
||||
}
|
||||
|
||||
void _showCustomers(BuildContext context) {
|
||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
||||
if (!InvenTreeAPI().checkConnection()) return;
|
||||
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"})));
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import "package:inventree/widget/paginator.dart";
|
||||
import "package:inventree/widget/part_detail.dart";
|
||||
import "package:inventree/widget/refreshable_state.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
|
||||
|
||||
|
@ -247,7 +247,7 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
||||
);
|
||||
*/
|
||||
|
||||
if (order.isPlaced && InvenTreeAPI().supportPoReceive()) {
|
||||
if (order.isPlaced && InvenTreeAPI().supportsPoReceive) {
|
||||
children.add(
|
||||
SimpleDialogOption(
|
||||
onPressed: () {
|
||||
|
@ -1,21 +1,19 @@
|
||||
|
||||
/*
|
||||
* Display a snackbar with:
|
||||
*
|
||||
* a) Text on the left
|
||||
* b) Icon on the right
|
||||
*
|
||||
* | Text <icon> |
|
||||
*/
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||
import "package:one_context/one_context.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
|
||||
|
||||
/*
|
||||
* Display a configurable 'snackbar' at the bottom of the screen
|
||||
*/
|
||||
void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) {
|
||||
|
||||
// Escape quickly if we do not have context
|
||||
if (!OneContext.hasContext) {
|
||||
return;
|
||||
}
|
||||
|
||||
BuildContext? context = OneContext().context;
|
||||
|
||||
if (context != null) {
|
||||
|
@ -19,10 +19,9 @@ import "package:inventree/widget/stock_item_history.dart";
|
||||
import "package:inventree/widget/stock_item_test_results.dart";
|
||||
import "package:inventree/widget/stock_notes.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/helpers.dart";
|
||||
import "package:inventree/api.dart";
|
||||
import "package:inventree/api_form.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
|
||||
|
||||
class StockDetailWidget extends StatefulWidget {
|
||||
@ -312,7 +311,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
Future <void> _addStockDialog() async {
|
||||
|
||||
// TODO: In future, deprecate support for older API
|
||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
||||
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||
|
||||
Map<String, dynamic> fields = {
|
||||
"pk": {
|
||||
@ -392,7 +391,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
void _removeStockDialog() {
|
||||
|
||||
// TODO: In future, deprecate support for the older API
|
||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
||||
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||
Map<String, dynamic> fields = {
|
||||
"pk": {
|
||||
"parent": "items",
|
||||
@ -464,7 +463,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
Future <void> _countStockDialog() async {
|
||||
|
||||
// TODO: In future, deprecate support for older API
|
||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
||||
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||
|
||||
Map<String, dynamic> fields = {
|
||||
"pk": {
|
||||
@ -567,7 +566,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
Future <void> _transferStockDialog(BuildContext context) async {
|
||||
|
||||
// TODO: In future, deprecate support for older API
|
||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
||||
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||
|
||||
Map<String, dynamic> fields = {
|
||||
"pk": {
|
||||
@ -1008,7 +1007,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||
}
|
||||
).then((result) {
|
||||
if (result) {
|
||||
successTone();
|
||||
barcodeSuccessTone();
|
||||
|
||||
showSnackIcon(
|
||||
L10().barcodeAssigned,
|
||||
|
@ -5,7 +5,7 @@ import "package:inventree/inventree/stock.dart";
|
||||
import "package:inventree/widget/paginator.dart";
|
||||
import "package:inventree/widget/refreshable_state.dart";
|
||||
import "package:inventree/l10.dart";
|
||||
import "package:inventree/app_settings.dart";
|
||||
import "package:inventree/preferences.dart";
|
||||
import "package:inventree/widget/stock_detail.dart";
|
||||
import "package:inventree/api.dart";
|
||||
|
||||
|
Reference in New Issue
Block a user