mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 13:36:50 +00:00
commit
d55f594342
3
.github/workflows/android.yaml
vendored
3
.github/workflows/android.yaml
vendored
@ -6,9 +6,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
70
.github/workflows/ci.yaml
vendored
Normal file
70
.github/workflows/ci.yaml
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
INVENTREE_DB_ENGINE: django.db.backends.sqlite3
|
||||||
|
INVENTREE_DB_NAME: ../inventree_unit_test_db.sqlite3
|
||||||
|
INVENTREE_MEDIA_ROOT: ../test_inventree_media
|
||||||
|
INVENTREE_STATIC_ROOT: ../test_inventree_static
|
||||||
|
INVENTREE_ADMIN_USER: testuser
|
||||||
|
INVENTREE_ADMIN_PASSWORD: testpassword
|
||||||
|
INVENTREE_ADMIN_EMAIL: test@test.com
|
||||||
|
INVENTREE_PYTHON_TEST_SERVER: http://localhost:12345
|
||||||
|
INVENTREE_PYTHON_TEST_USERNAME: testuser
|
||||||
|
INVENTREE_PYTHON_TEST_PASSWORD: testpassword
|
||||||
|
jobs:
|
||||||
|
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v1
|
||||||
|
with:
|
||||||
|
java-version: '12.x'
|
||||||
|
- name: Setup Flutter
|
||||||
|
uses: subosito/flutter-action@v1
|
||||||
|
with:
|
||||||
|
flutter-version: '2.10.3'
|
||||||
|
- name: Collect Translation Files
|
||||||
|
run: |
|
||||||
|
cd lib/l10n
|
||||||
|
python3 collect_translations.py
|
||||||
|
- name: Static Analysis Tests
|
||||||
|
run: |
|
||||||
|
cp lib/dummy_dsn.dart lib/dsn.dart
|
||||||
|
python3 find_dart_files.py
|
||||||
|
flutter pub get
|
||||||
|
flutter analyze
|
||||||
|
|
||||||
|
- name: Start InvenTree Server
|
||||||
|
run: |
|
||||||
|
sudo apt-get install python3-dev python3-pip python3-venv python3-wheel g++
|
||||||
|
pip3 install invoke
|
||||||
|
git clone --depth 1 https://github.com/inventree/inventree ./inventree_server
|
||||||
|
cd inventree_server
|
||||||
|
invoke install
|
||||||
|
invoke migrate
|
||||||
|
invoke import-fixtures
|
||||||
|
invoke server -a 127.0.0.1:12345 &
|
||||||
|
invoke wait
|
||||||
|
- name: Unit Tests
|
||||||
|
run: |
|
||||||
|
flutter test --coverage
|
||||||
|
|
||||||
|
- name: Coveralls
|
||||||
|
uses: coverallsapp/github-action@master
|
||||||
|
with:
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
3
.github/workflows/ios.yaml
vendored
3
.github/workflows/ios.yaml
vendored
@ -6,9 +6,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- master
|
- master
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
|
41
.github/workflows/lint.yaml
vendored
41
.github/workflows/lint.yaml
vendored
@ -1,41 +0,0 @@
|
|||||||
# Run flutter linting checks
|
|
||||||
|
|
||||||
name: lint
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
|
|
||||||
lint:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
|
|
||||||
env:
|
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v2
|
|
||||||
with:
|
|
||||||
submodules: recursive
|
|
||||||
- name: Setup Java
|
|
||||||
uses: actions/setup-java@v1
|
|
||||||
with:
|
|
||||||
java-version: '12.x'
|
|
||||||
- name: Setup Flutter
|
|
||||||
uses: subosito/flutter-action@v1
|
|
||||||
with:
|
|
||||||
flutter-version: '2.10.3'
|
|
||||||
- name: Collect Translation Files
|
|
||||||
run: |
|
|
||||||
cd lib/l10n
|
|
||||||
python3 collect_translations.py
|
|
||||||
- run: flutter pub get
|
|
||||||
- run: cp lib/dummy_dsn.dart lib/dsn.dart
|
|
||||||
- run: flutter analyze
|
|
||||||
- run: flutter test --coverage
|
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -11,6 +11,9 @@
|
|||||||
|
|
||||||
coverage/*
|
coverage/*
|
||||||
|
|
||||||
|
# This file is auto-generated as part of the CI process
|
||||||
|
test/coverage_helper_test.dart
|
||||||
|
|
||||||
# Sentry API key
|
# Sentry API key
|
||||||
lib/dsn.dart
|
lib/dsn.dart
|
||||||
|
|
||||||
|
46
find_dart_files.py
Normal file
46
find_dart_files.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
"""
|
||||||
|
This script recursively finds any '.dart' files in the ./lib directory,
|
||||||
|
and generates a 'test' file which includes all these files.
|
||||||
|
|
||||||
|
This is to ensure that *all* .dart files are included in test coverage.
|
||||||
|
By default, source files which are not touched by the unit tests are not included!
|
||||||
|
|
||||||
|
Ref: https://github.com/flutter/flutter/issues/27997
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
dart_files = Path('lib').rglob('*.dart')
|
||||||
|
|
||||||
|
with open("test/coverage_helper_test.dart", "w") as f:
|
||||||
|
|
||||||
|
f.write("// ignore_for_file: unused_import\n\n")
|
||||||
|
|
||||||
|
skips = [
|
||||||
|
'generated',
|
||||||
|
'l10n',
|
||||||
|
'dsn.dart',
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in dart_files:
|
||||||
|
path = str(path)
|
||||||
|
|
||||||
|
if any([s in path for s in skips]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Remove leading 'lib\' text
|
||||||
|
path = path[4:]
|
||||||
|
path = path.replace('\\', '/')
|
||||||
|
f.write(f'import "package:inventree/{path}";\n')
|
||||||
|
|
||||||
|
f.write("\n\n")
|
||||||
|
|
||||||
|
f.write("// DO NOT EDIT THIS FILE - it has been auto-generated by 'find_dart_files.py'\n")
|
||||||
|
f.write("// It has been created to ensure that *all* source file are included in coverage data\n")
|
||||||
|
|
||||||
|
f.write('import "package:test/test.dart";\n\n');
|
||||||
|
|
||||||
|
f.write("// Do not actually test anything!\n")
|
||||||
|
f.write("void main() {}\n")
|
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:http/http.dart" as http;
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
import "package:inventree/app_colors.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:open_file/open_file.dart";
|
||||||
import "package:cached_network_image/cached_network_image.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/widget/dialogs.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/inventree/sentry.dart";
|
import "package:inventree/inventree/sentry.dart";
|
||||||
import "package:inventree/inventree/model.dart";
|
import "package:inventree/inventree/model.dart";
|
||||||
import "package:inventree/user_profile.dart";
|
import "package:inventree/user_profile.dart";
|
||||||
@ -201,11 +202,13 @@ class InvenTreeAPI {
|
|||||||
// Authentication token (initially empty, must be requested)
|
// Authentication token (initially empty, must be requested)
|
||||||
String _token = "";
|
String _token = "";
|
||||||
|
|
||||||
|
bool get hasToken => _token.isNotEmpty;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check server connection and display messages if not connected.
|
* Check server connection and display messages if not connected.
|
||||||
* Useful as a precursor check before performing operations.
|
* Useful as a precursor check before performing operations.
|
||||||
*/
|
*/
|
||||||
bool checkConnection(BuildContext context) {
|
bool checkConnection() {
|
||||||
// Firstly, is the server connected?
|
// Firstly, is the server connected?
|
||||||
if (!isConnected()) {
|
if (!isConnected()) {
|
||||||
|
|
||||||
@ -278,7 +281,7 @@ class InvenTreeAPI {
|
|||||||
bool _connecting = false;
|
bool _connecting = false;
|
||||||
|
|
||||||
bool isConnected() {
|
bool isConnected() {
|
||||||
return profile != null && _connected && baseUrl.isNotEmpty && _token.isNotEmpty;
|
return profile != null && _connected && baseUrl.isNotEmpty && hasToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isConnecting() {
|
bool isConnecting() {
|
||||||
@ -289,14 +292,10 @@ class InvenTreeAPI {
|
|||||||
static final InvenTreeAPI _api = InvenTreeAPI._internal();
|
static final InvenTreeAPI _api = InvenTreeAPI._internal();
|
||||||
|
|
||||||
// API endpoint for receiving purchase order line items was introduced in v12
|
// API endpoint for receiving purchase order line items was introduced in v12
|
||||||
bool supportPoReceive() {
|
bool get supportsPoReceive => apiVersion >= 12;
|
||||||
return apiVersion >= 12;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Modern" API transactions were implemented in API v14
|
// "Modern" API transactions were implemented in API v14
|
||||||
bool supportModernStockTransactions() {
|
bool get supportsModernStockTransactions => apiVersion >= 14;
|
||||||
return apiVersion >= 14;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Connect to the remote InvenTree server:
|
* Connect to the remote InvenTree server:
|
||||||
@ -338,7 +337,7 @@ class InvenTreeAPI {
|
|||||||
// Clear the list of available plugins
|
// Clear the list of available plugins
|
||||||
_plugins.clear();
|
_plugins.clear();
|
||||||
|
|
||||||
print("Connecting to ${apiUrl} -> username=${username}");
|
debug("Connecting to ${apiUrl} -> username=${username}");
|
||||||
|
|
||||||
APIResponse response;
|
APIResponse response;
|
||||||
|
|
||||||
@ -431,7 +430,7 @@ class InvenTreeAPI {
|
|||||||
// Return the received token
|
// Return the received token
|
||||||
_token = (data["token"] ?? "") as String;
|
_token = (data["token"] ?? "") as String;
|
||||||
|
|
||||||
print("Received token - $_token");
|
debug("Received token from server");
|
||||||
|
|
||||||
// Request user role information (async)
|
// Request user role information (async)
|
||||||
getUserRoles();
|
getUserRoles();
|
||||||
@ -445,7 +444,7 @@ class InvenTreeAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void disconnectFromServer() {
|
void disconnectFromServer() {
|
||||||
print("InvenTreeAPI().disconnectFromServer()");
|
debug("API : disconnectFromServer()");
|
||||||
|
|
||||||
_connected = false;
|
_connected = false;
|
||||||
_connecting = false;
|
_connecting = false;
|
||||||
@ -501,7 +500,7 @@ class InvenTreeAPI {
|
|||||||
|
|
||||||
roles.clear();
|
roles.clear();
|
||||||
|
|
||||||
print("Requesting user role data");
|
debug("API: Requesting user role data");
|
||||||
|
|
||||||
// Next we request the permissions assigned to the current user
|
// Next we request the permissions assigned to the current user
|
||||||
// Note: 2021-02-27 this "roles" feature for the API was just introduced.
|
// Note: 2021-02-27 this "roles" feature for the API was just introduced.
|
||||||
@ -531,7 +530,7 @@ class InvenTreeAPI {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
print("Requesting plugin information");
|
debug("API: getPluginInformation()");
|
||||||
|
|
||||||
// Request a list of plugins from the server
|
// Request a list of plugins from the server
|
||||||
final List<InvenTreeModel> results = await InvenTreePlugin().list();
|
final List<InvenTreeModel> results = await InvenTreePlugin().list();
|
||||||
@ -661,7 +660,7 @@ class InvenTreeAPI {
|
|||||||
_request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale());
|
_request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale());
|
||||||
|
|
||||||
} on SocketException catch (error) {
|
} on SocketException catch (error) {
|
||||||
print("SocketException at ${url}: ${error.toString()}");
|
debug("SocketException at ${url}: ${error.toString()}");
|
||||||
showServerError(L10().connectionRefused, error.toString());
|
showServerError(L10().connectionRefused, error.toString());
|
||||||
return;
|
return;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
@ -670,7 +669,7 @@ class InvenTreeAPI {
|
|||||||
return;
|
return;
|
||||||
} on HandshakeException catch (error) {
|
} on HandshakeException catch (error) {
|
||||||
print("HandshakeException at ${url}:");
|
print("HandshakeException at ${url}:");
|
||||||
print(error.toString());
|
debug(error.toString());
|
||||||
showServerError(L10().serverCertificateError, error.toString());
|
showServerError(L10().serverCertificateError, error.toString());
|
||||||
return;
|
return;
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
@ -1233,8 +1232,6 @@ class InvenTreeAPI {
|
|||||||
|
|
||||||
var plugins = getPlugins(mixin: "locate");
|
var plugins = getPlugins(mixin: "locate");
|
||||||
|
|
||||||
print("locateItemOrLocation");
|
|
||||||
|
|
||||||
if (plugins.isEmpty) {
|
if (plugins.isEmpty) {
|
||||||
// TODO: Error message
|
// TODO: Error message
|
||||||
return;
|
return;
|
||||||
|
@ -335,7 +335,7 @@ class APIFormField {
|
|||||||
controller.text = hash;
|
controller.text = hash;
|
||||||
data["value"] = hash;
|
data["value"] = hash;
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeAssigned,
|
L10().barcodeAssigned,
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
|
|
||||||
|
|
||||||
import "dart:ui";
|
import "dart:ui";
|
||||||
|
|
||||||
const Color COLOR_GRAY = Color.fromRGBO(50, 50, 50, 1);
|
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/stock.dart";
|
||||||
import "package:inventree/inventree/part.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/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/location_display.dart";
|
||||||
import "package:inventree/widget/part_detail.dart";
|
import "package:inventree/widget/part_detail.dart";
|
||||||
import "package:inventree/widget/stock_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 BarcodeHandler {
|
||||||
/*
|
/*
|
||||||
* Class which "handles" a barcode, by communicating with the InvenTree server,
|
* 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
|
// Called when the server does not know about a barcode
|
||||||
// Override this function
|
// Override this function
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeNoMatch,
|
L10().barcodeNoMatch,
|
||||||
@ -55,7 +78,7 @@ class BarcodeHandler {
|
|||||||
|
|
||||||
Future<void> onBarcodeUnhandled(BuildContext context, Map<String, dynamic> data) async {
|
Future<void> onBarcodeUnhandled(BuildContext context, Map<String, dynamic> data) async {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
// Called when the server returns an unhandled response
|
// Called when the server returns an unhandled response
|
||||||
showServerError(L10().responseUnknown, data.toString());
|
showServerError(L10().responseUnknown, data.toString());
|
||||||
@ -125,7 +148,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
@override
|
@override
|
||||||
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
|
Future<void> onBarcodeUnknown(BuildContext context, Map<String, dynamic> data) async {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeNoMatch,
|
L10().barcodeNoMatch,
|
||||||
@ -146,7 +169,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (pk > 0) {
|
if (pk > 0) {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
InvenTreeStockLocation().get(pk).then((var loc) {
|
InvenTreeStockLocation().get(pk).then((var loc) {
|
||||||
if (loc is InvenTreeStockLocation) {
|
if (loc is InvenTreeStockLocation) {
|
||||||
@ -156,7 +179,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().invalidStockLocation,
|
L10().invalidStockLocation,
|
||||||
@ -170,7 +193,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (pk > 0) {
|
if (pk > 0) {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
InvenTreeStockItem().get(pk).then((var item) {
|
InvenTreeStockItem().get(pk).then((var item) {
|
||||||
|
|
||||||
@ -183,7 +206,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().invalidStockItem,
|
L10().invalidStockItem,
|
||||||
@ -196,7 +219,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (pk > 0) {
|
if (pk > 0) {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
InvenTreePart().get(pk).then((var part) {
|
InvenTreePart().get(pk).then((var part) {
|
||||||
|
|
||||||
@ -209,7 +232,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().invalidPart,
|
L10().invalidPart,
|
||||||
@ -218,7 +241,7 @@ class BarcodeScanHandler extends BarcodeHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeUnknown,
|
L10().barcodeUnknown,
|
||||||
@ -275,7 +298,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
||||||
@ -285,7 +308,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeScanIntoLocationFailure,
|
L10().barcodeScanIntoLocationFailure,
|
||||||
@ -294,7 +317,7 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().invalidStockLocation,
|
L10().invalidStockLocation,
|
||||||
@ -329,14 +352,14 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().invalidStockItem,
|
L10().invalidStockItem,
|
||||||
success: false,
|
success: false,
|
||||||
);
|
);
|
||||||
} else if (item.locationId == location.pk) {
|
} else if (item.locationId == location.pk) {
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().itemInLocation,
|
L10().itemInLocation,
|
||||||
@ -347,7 +370,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
|||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeScanIntoLocationSuccess,
|
L10().barcodeScanIntoLocationSuccess,
|
||||||
@ -355,7 +378,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeScanIntoLocationFailure,
|
L10().barcodeScanIntoLocationFailure,
|
||||||
@ -365,7 +388,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
// Does not match a valid stock item!
|
// Does not match a valid stock item!
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
@ -401,7 +424,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
|||||||
@override
|
@override
|
||||||
Future<void> onBarcodeMatched(BuildContext context, Map<String, dynamic> data) async {
|
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!
|
// If the barcode is known, we can"t assign it to the stock item!
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
@ -424,7 +447,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
|||||||
String hash = (data["hash"] ?? "") as String;
|
String hash = (data["hash"] ?? "") as String;
|
||||||
|
|
||||||
if (hash.isEmpty) {
|
if (hash.isEmpty) {
|
||||||
failureTone();
|
barcodeFailureTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeError,
|
L10().barcodeError,
|
||||||
@ -432,7 +455,7 @@ class UniqueBarcodeHandler extends BarcodeHandler {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
// Close the barcode scanner
|
// Close the barcode scanner
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
@ -7,8 +7,22 @@
|
|||||||
* supressing trailing zeroes
|
* supressing trailing zeroes
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import "dart:io";
|
||||||
|
|
||||||
import "package:audioplayers/audioplayers.dart";
|
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) {
|
String simpleNumberString(double number) {
|
||||||
// Ref: https://stackoverflow.com/questions/55152175/how-to-remove-trailing-zeros-using-dart
|
// 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);
|
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 (!OneContext.hasContext) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (en) {
|
|
||||||
final player = AudioCache();
|
final player = AudioCache();
|
||||||
player.play("sounds/barcode_scan.mp3");
|
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 "dart:io";
|
||||||
|
|
||||||
import "package:device_info_plus/device_info_plus.dart";
|
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:package_info_plus/package_info_plus.dart";
|
||||||
import "package:sentry_flutter/sentry_flutter.dart";
|
import "package:sentry_flutter/sentry_flutter.dart";
|
||||||
|
|
||||||
|
@ -533,7 +533,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
Map<String, dynamic> data = {};
|
Map<String, dynamic> data = {};
|
||||||
|
|
||||||
// Note: Format of adjustment API was updated in API v14
|
// Note: Format of adjustment API was updated in API v14
|
||||||
if (api.supportModernStockTransactions()) {
|
if (api.supportsModernStockTransactions) {
|
||||||
// Modern (> 14) API
|
// Modern (> 14) API
|
||||||
data = {
|
data = {
|
||||||
"items": [
|
"items": [
|
||||||
@ -560,7 +560,7 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Expected API return code depends on server API version
|
// 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(
|
var response = await api.post(
|
||||||
endpoint,
|
endpoint,
|
||||||
|
@ -7,6 +7,7 @@ import "package:flutter/material.dart";
|
|||||||
// Shortcut function to reduce boilerplate!
|
// Shortcut function to reduce boilerplate!
|
||||||
I18N L10()
|
I18N L10()
|
||||||
{
|
{
|
||||||
|
if (OneContext.hasContext) {
|
||||||
BuildContext? _ctx = OneContext().context;
|
BuildContext? _ctx = OneContext().context;
|
||||||
|
|
||||||
if (_ctx != null) {
|
if (_ctx != null) {
|
||||||
@ -16,6 +17,7 @@ I18N L10()
|
|||||||
return i18n;
|
return i18n;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Fallback for "null" context
|
// Fallback for "null" context
|
||||||
return I18NEn();
|
return I18NEn();
|
||||||
|
@ -6,6 +6,26 @@ import "package:sembast/sembast_io.dart";
|
|||||||
import "package:path/path.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
|
* 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
|
// Get a platform-specific directory where persistent app data can be stored
|
||||||
final appDocumentDir = await getApplicationDocumentsDirectory();
|
final appDocumentDir = await getApplicationDocumentsDirectory();
|
||||||
|
|
||||||
print("Documents Dir: ${appDocumentDir.toString()}");
|
|
||||||
|
|
||||||
print("Path: ${appDocumentDir.path}");
|
|
||||||
|
|
||||||
// Path with the form: /platform-specific-directory/demo.db
|
// Path with the form: /platform-specific-directory/demo.db
|
||||||
final dbPath = join(appDocumentDir.path, "InvenTreeSettings.db");
|
final dbPath = join(appDocumentDir.path, "InvenTreeSettings.db");
|
||||||
|
|
||||||
final database = await databaseFactoryIo.openDatabase(dbPath);
|
final database = await databaseFactoryIo.openDatabase(dbPath);
|
||||||
|
|
||||||
// Any code awaiting the Completer's future will now start executing
|
|
||||||
_dbOpenCompleter.complete(database);
|
_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
|
||||||
|
|
||||||
InvenTreePreferences._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.
|
|
||||||
*/
|
*/
|
||||||
|
class InvenTreeSettingsManager {
|
||||||
|
|
||||||
// Expand subcategory list in PartCategory view
|
factory InvenTreeSettingsManager() {
|
||||||
bool expandCategoryList = false;
|
return _manager;
|
||||||
|
}
|
||||||
// Expand part list in PartCategory view
|
|
||||||
bool expandPartList = true;
|
InvenTreeSettingsManager._internal();
|
||||||
|
|
||||||
// Expand sublocation list in StockLocation view
|
final store = StoreRef("settings");
|
||||||
bool expandLocationList = false;
|
|
||||||
|
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
|
||||||
// Expand item list in StockLocation view
|
|
||||||
bool expandStockList = true;
|
Future<dynamic> getValue(String key, dynamic backup) async {
|
||||||
|
|
||||||
// Ensure we only ever create a single instance of the preferences class
|
final value = await store.record(key).get(await _db);
|
||||||
static final InvenTreePreferences _api = InvenTreePreferences._internal();
|
|
||||||
|
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();
|
||||||
}
|
}
|
@ -3,7 +3,7 @@ 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/app_settings.dart";
|
import "package:inventree/preferences.dart";
|
||||||
|
|
||||||
|
|
||||||
class InvenTreeAppSettingsWidget extends StatefulWidget {
|
class InvenTreeAppSettingsWidget extends StatefulWidget {
|
||||||
|
@ -5,7 +5,7 @@ import "package:inventree/l10.dart";
|
|||||||
|
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.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 {
|
class HomeScreenSettingsWidget extends StatefulWidget {
|
||||||
@override
|
@override
|
||||||
|
@ -344,7 +344,6 @@ class _ProfileEditState extends State<ProfileEditWidget> {
|
|||||||
Uri uri = Uri.parse(value);
|
Uri uri = Uri.parse(value);
|
||||||
|
|
||||||
if (uri.hasScheme) {
|
if (uri.hasScheme) {
|
||||||
print("Scheme: ${uri.scheme}");
|
|
||||||
if (!["http", "https"].contains(uri.scheme.toLowerCase())) {
|
if (!["http", "https"].contains(uri.scheme.toLowerCase())) {
|
||||||
return L10().serverStart;
|
return L10().serverStart;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import "package:sembast/sembast.dart";
|
import "package:sembast/sembast.dart";
|
||||||
|
|
||||||
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/preferences.dart";
|
import "package:inventree/preferences.dart";
|
||||||
|
|
||||||
class UserProfile {
|
class UserProfile {
|
||||||
@ -62,75 +63,94 @@ class UserProfileDBManager {
|
|||||||
|
|
||||||
Future<Database> get _db async => InvenTreePreferencesDB.instance.database;
|
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 {
|
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 profiles.isNotEmpty;
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addProfile(UserProfile profile) async {
|
// No match found!
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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
|
// Check if a profile already exists with the name
|
||||||
final bool exists = await profileNameExists(profile.name);
|
final bool exists = await profileNameExists(profile.name);
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
print("UserProfile '${profile.name}' already exists");
|
debug("addProfile() : UserProfile '${profile.name}' already exists");
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int key = await store.add(await _db, profile.toJson()) as int;
|
int key = await store.add(await _db, profile.toJson()) as int;
|
||||||
|
|
||||||
print("Added user profile <${key}> - '${profile.name}'");
|
|
||||||
|
|
||||||
// Record the key
|
// Record the key
|
||||||
profile.key = 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);
|
// Prevent invalid profile data from being updated
|
||||||
|
if (profile.name.isEmpty || profile.username.isEmpty || profile.password.isEmpty) {
|
||||||
return result;
|
debug("updateProfile() : Profile missing required values - not updating");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> updateProfile(UserProfile profile) async {
|
|
||||||
|
|
||||||
if (profile.key == null) {
|
if (profile.key == null) {
|
||||||
await addProfile(profile);
|
bool result = await addProfile(profile);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await store.record(profile.key).update(await _db, profile.toJson());
|
|
||||||
|
|
||||||
print("Updated user profile <${profile.key}> - '${profile.name}'");
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteProfile(UserProfile profile) async {
|
await store.record(profile.key).update(await _db, profile.toJson());
|
||||||
await store.record(profile.key).delete(await _db);
|
|
||||||
print("Deleted user profile <${profile.key}> - '${profile.name}'");
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove a user profile from the database
|
||||||
|
*/
|
||||||
|
Future<void> deleteProfile(UserProfile profile) async {
|
||||||
|
await store.record(profile.key).delete(await _db);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserProfile?> getSelectedProfile() async {
|
|
||||||
/*
|
/*
|
||||||
* Return the currently selected profile.
|
* Return the currently selected profile.
|
||||||
*
|
* The key of the UserProfile should match the "selected" property
|
||||||
* key should match the "selected" property
|
|
||||||
*/
|
*/
|
||||||
|
Future<UserProfile?> getSelectedProfile() async {
|
||||||
|
|
||||||
final selected = await store.record("selected").get(await _db);
|
final selected = await store.record("selected").get(await _db);
|
||||||
|
|
||||||
final profiles = await store.find(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++) {
|
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) {
|
if (profiles[idx].key is int && profiles[idx].key == selected) {
|
||||||
return UserProfile.fromJson(
|
return UserProfile.fromJson(
|
||||||
profiles[idx].key as int,
|
profiles[idx].key as int,
|
||||||
@ -162,10 +182,39 @@ class UserProfileDBManager {
|
|||||||
profiles[idx].key as int,
|
profiles[idx].key as int,
|
||||||
profiles[idx].value as Map<String, dynamic>,
|
profiles[idx].value as Map<String, dynamic>,
|
||||||
profiles[idx].key == selected,
|
profiles[idx].key == selected,
|
||||||
));
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return profileList;
|
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: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/helpers.dart";
|
||||||
import "package:one_context/one_context.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 {
|
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;
|
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;
|
final bool tones = await InvenTreeSettingsManager().getValue(INV_SOUNDS_SERVER, true) as bool;
|
||||||
|
|
||||||
if (tones) {
|
if (tones) {
|
||||||
final player = AudioCache();
|
playAudioFile("sounds/server_error.mp3");
|
||||||
player.play("sounds/server_error.mp3");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
|
@ -34,7 +34,7 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
|
|
||||||
void _search() {
|
void _search() {
|
||||||
|
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
@ -51,7 +51,7 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
* Upon successful scan, data are passed off to be decoded.
|
* Upon successful scan, data are passed off to be decoded.
|
||||||
*/
|
*/
|
||||||
Future <void> _scan() async {
|
Future <void> _scan() async {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
scanQrCode(context);
|
scanQrCode(context);
|
||||||
|
@ -6,7 +6,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
|||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/app_colors.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/barcode.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
import "package:inventree/settings/login.dart";
|
import "package:inventree/settings/login.dart";
|
||||||
@ -71,13 +71,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
UserProfile? _profile;
|
UserProfile? _profile;
|
||||||
|
|
||||||
void _scan(BuildContext context) {
|
void _scan(BuildContext context) {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
scanQrCode(context);
|
scanQrCode(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showParts(BuildContext context) {
|
void _showParts(BuildContext context) {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showStarredParts(BuildContext context) {
|
void _showStarredParts(BuildContext context) {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@ -100,13 +100,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _showStock(BuildContext context) {
|
void _showStock(BuildContext context) {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showPurchaseOrders(BuildContext context) {
|
void _showPurchaseOrders(BuildContext context) {
|
||||||
if (!InvenTreeAPI().checkConnection(context)) return;
|
if (!InvenTreeAPI().checkConnection()) return;
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
@ -118,19 +118,19 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
void _showSuppliers(BuildContext context) {
|
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"})));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"})));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showManufacturers(BuildContext context) {
|
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"})));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().manufacturers, {"is_manufacturer": "true"})));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showCustomers(BuildContext context) {
|
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"})));
|
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/part_detail.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/app_settings.dart";
|
import "package:inventree/preferences.dart";
|
||||||
import "package:inventree/l10.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(
|
children.add(
|
||||||
SimpleDialogOption(
|
SimpleDialogOption(
|
||||||
onPressed: () {
|
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:flutter/material.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
import "package:inventree/l10.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}) {
|
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;
|
BuildContext? context = OneContext().context;
|
||||||
|
|
||||||
if (context != null) {
|
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_item_test_results.dart";
|
||||||
import "package:inventree/widget/stock_notes.dart";
|
import "package:inventree/widget/stock_notes.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
import "package:inventree/helpers.dart";
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/api_form.dart";
|
import "package:inventree/api_form.dart";
|
||||||
import "package:inventree/app_settings.dart";
|
import "package:inventree/preferences.dart";
|
||||||
|
|
||||||
|
|
||||||
class StockDetailWidget extends StatefulWidget {
|
class StockDetailWidget extends StatefulWidget {
|
||||||
@ -312,7 +311,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
Future <void> _addStockDialog() async {
|
Future <void> _addStockDialog() async {
|
||||||
|
|
||||||
// TODO: In future, deprecate support for older API
|
// TODO: In future, deprecate support for older API
|
||||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||||
|
|
||||||
Map<String, dynamic> fields = {
|
Map<String, dynamic> fields = {
|
||||||
"pk": {
|
"pk": {
|
||||||
@ -392,7 +391,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
void _removeStockDialog() {
|
void _removeStockDialog() {
|
||||||
|
|
||||||
// TODO: In future, deprecate support for the older API
|
// TODO: In future, deprecate support for the older API
|
||||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||||
Map<String, dynamic> fields = {
|
Map<String, dynamic> fields = {
|
||||||
"pk": {
|
"pk": {
|
||||||
"parent": "items",
|
"parent": "items",
|
||||||
@ -464,7 +463,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
Future <void> _countStockDialog() async {
|
Future <void> _countStockDialog() async {
|
||||||
|
|
||||||
// TODO: In future, deprecate support for older API
|
// TODO: In future, deprecate support for older API
|
||||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||||
|
|
||||||
Map<String, dynamic> fields = {
|
Map<String, dynamic> fields = {
|
||||||
"pk": {
|
"pk": {
|
||||||
@ -567,7 +566,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
Future <void> _transferStockDialog(BuildContext context) async {
|
Future <void> _transferStockDialog(BuildContext context) async {
|
||||||
|
|
||||||
// TODO: In future, deprecate support for older API
|
// TODO: In future, deprecate support for older API
|
||||||
if (InvenTreeAPI().supportModernStockTransactions()) {
|
if (InvenTreeAPI().supportsModernStockTransactions) {
|
||||||
|
|
||||||
Map<String, dynamic> fields = {
|
Map<String, dynamic> fields = {
|
||||||
"pk": {
|
"pk": {
|
||||||
@ -1008,7 +1007,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
}
|
}
|
||||||
).then((result) {
|
).then((result) {
|
||||||
if (result) {
|
if (result) {
|
||||||
successTone();
|
barcodeSuccessTone();
|
||||||
|
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
L10().barcodeAssigned,
|
L10().barcodeAssigned,
|
||||||
|
@ -5,7 +5,7 @@ import "package:inventree/inventree/stock.dart";
|
|||||||
import "package:inventree/widget/paginator.dart";
|
import "package:inventree/widget/paginator.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/l10.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/widget/stock_detail.dart";
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
|
|
||||||
|
189
pubspec.lock
189
pubspec.lock
@ -1,6 +1,20 @@
|
|||||||
# Generated by pub
|
# Generated by pub
|
||||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||||
packages:
|
packages:
|
||||||
|
_fe_analyzer_shared:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: _fe_analyzer_shared
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "31.0.0"
|
||||||
|
analyzer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: analyzer
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.8.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -99,6 +113,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.1"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.5"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -113,6 +134,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.15.0"
|
version: "1.15.0"
|
||||||
|
convert:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: convert
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.1"
|
||||||
|
coverage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: coverage
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -280,6 +315,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.2.0"
|
version: "9.2.0"
|
||||||
|
frontend_server_client:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: frontend_server_client
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.3"
|
||||||
|
glob:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: glob
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
http:
|
http:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -287,6 +336,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.4"
|
version: "0.13.4"
|
||||||
|
http_multi_server:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http_multi_server
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.0"
|
||||||
http_parser:
|
http_parser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -336,6 +392,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.17.0"
|
version: "0.17.0"
|
||||||
|
io:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: io
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.3"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -350,6 +413,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.8.2"
|
version: "1.8.2"
|
||||||
|
logging:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: logging
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
markdown:
|
markdown:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -378,6 +448,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
|
mime:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: mime
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
node_preamble:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: node_preamble
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
octo_image:
|
octo_image:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -399,6 +483,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
|
package_config:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: package_config
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -525,6 +616,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.2"
|
version: "2.1.2"
|
||||||
|
pool:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pool
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.5.0"
|
||||||
process:
|
process:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -532,6 +630,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "4.2.4"
|
||||||
|
pub_semver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: pub_semver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.1"
|
||||||
qr_code_scanner:
|
qr_code_scanner:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -630,6 +735,34 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
|
shelf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.0"
|
||||||
|
shelf_packages_handler:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_packages_handler
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
|
shelf_static:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_static
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
|
shelf_web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shelf_web_socket
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -642,6 +775,20 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.5"
|
version: "0.2.5"
|
||||||
|
source_map_stack_trace:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_map_stack_trace
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
source_maps:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: source_maps
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.10.10"
|
||||||
source_span:
|
source_span:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -705,6 +852,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: test
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.19.5"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -712,6 +866,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.8"
|
version: "0.4.8"
|
||||||
|
test_core:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: test_core
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.9"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -789,6 +950,34 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.1"
|
||||||
|
vm_service:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: vm_service
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "7.5.0"
|
||||||
|
watcher:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: watcher
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.1"
|
||||||
|
web_socket_channel:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket_channel
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.0"
|
||||||
|
webkit_inspection_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webkit_inspection_protocol
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
win32:
|
win32:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
31
pubspec.yaml
31
pubspec.yaml
@ -38,10 +38,11 @@ dependencies:
|
|||||||
url_launcher: ^6.0.9 # Open link in system browser
|
url_launcher: ^6.0.9 # Open link in system browser
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons: ^0.9.0
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
lint: ^1.0.0
|
lint: ^1.8.0
|
||||||
|
test: ^1.19.0
|
||||||
|
|
||||||
flutter_icons:
|
flutter_icons:
|
||||||
android: true
|
android: true
|
||||||
@ -64,29 +65,3 @@ flutter:
|
|||||||
- assets/sounds/barcode_scan.mp3
|
- assets/sounds/barcode_scan.mp3
|
||||||
- assets/sounds/barcode_error.mp3
|
- assets/sounds/barcode_error.mp3
|
||||||
- assets/sounds/server_error.mp3
|
- assets/sounds/server_error.mp3
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Python requirements for devops
|
||||||
|
|
||||||
|
coverage==5.3 # Unit test coverage
|
||||||
|
coveralls==2.1.2 # Coveralls linking (for code coverage reporting)
|
141
test/api_test.dart
Normal file
141
test/api_test.dart
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Unit tests for the InvenTree API code
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "package:test/test.dart";
|
||||||
|
|
||||||
|
import "package:inventree/api.dart";
|
||||||
|
import "package:inventree/user_profile.dart";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
|
||||||
|
if (! await UserProfileDBManager().profileNameExists("Test Profile")) {
|
||||||
|
// Create and select a profile to user
|
||||||
|
|
||||||
|
print("TEST: Creating profile for user 'testuser'");
|
||||||
|
|
||||||
|
await UserProfileDBManager().addProfile(UserProfile(
|
||||||
|
name: "Test Profile",
|
||||||
|
server: "http://localhost:12345",
|
||||||
|
username: "testuser",
|
||||||
|
password: "testpassword",
|
||||||
|
selected: true,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
var prf = await UserProfileDBManager().getSelectedProfile();
|
||||||
|
|
||||||
|
// Ensure that the server settings are correct by default,
|
||||||
|
// as they can get overwritten by subsequent tests
|
||||||
|
|
||||||
|
if (prf != null) {
|
||||||
|
prf.name = "Test Profile";
|
||||||
|
prf.server = "http://localhost:12345";
|
||||||
|
prf.username = "testuser";
|
||||||
|
prf.password = "testpassword";
|
||||||
|
|
||||||
|
await UserProfileDBManager().updateProfile(prf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the profile is selected
|
||||||
|
assert(! await UserProfileDBManager().selectProfileByName("Missing Profile"));
|
||||||
|
assert(await UserProfileDBManager().selectProfileByName("Test Profile"));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
group("Login Tests:", () {
|
||||||
|
|
||||||
|
test("Disconnected", () async {
|
||||||
|
// Test that calling disconnect() does the right thing
|
||||||
|
var api = InvenTreeAPI();
|
||||||
|
|
||||||
|
api.disconnectFromServer();
|
||||||
|
|
||||||
|
// Check expected values
|
||||||
|
expect(api.isConnected(), equals(false));
|
||||||
|
expect(api.isConnecting(), equals(false));
|
||||||
|
expect(api.hasToken, equals(false));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Login Failure", () async {
|
||||||
|
// Tests for various types of login failures
|
||||||
|
var api = InvenTreeAPI();
|
||||||
|
|
||||||
|
// Incorrect server address
|
||||||
|
var profile = await UserProfileDBManager().getSelectedProfile();
|
||||||
|
|
||||||
|
assert(profile != null);
|
||||||
|
|
||||||
|
if (profile != null) {
|
||||||
|
profile.server = "http://localhost:5555";
|
||||||
|
await UserProfileDBManager().updateProfile(profile);
|
||||||
|
|
||||||
|
bool result = await api.connectToServer();
|
||||||
|
assert(!result);
|
||||||
|
|
||||||
|
// TODO: Test the the right 'error message' is returned
|
||||||
|
// TODO: The request above should throw a 'SockeException'
|
||||||
|
|
||||||
|
// Test incorrect login details
|
||||||
|
profile.server = "http://localhost:12345";
|
||||||
|
profile.username = "invalidusername";
|
||||||
|
|
||||||
|
await UserProfileDBManager().updateProfile(profile);
|
||||||
|
|
||||||
|
await api.connectToServer();
|
||||||
|
assert(!result);
|
||||||
|
|
||||||
|
// TODO: Test that the connection attempt above throws an authentication error
|
||||||
|
|
||||||
|
assert(!api.checkConnection());
|
||||||
|
|
||||||
|
} else {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Login Success", () async {
|
||||||
|
// Test that we can login to the server successfully
|
||||||
|
var api = InvenTreeAPI();
|
||||||
|
|
||||||
|
// Attempt to connect
|
||||||
|
final bool result = await api.connectToServer();
|
||||||
|
|
||||||
|
// Check expected values
|
||||||
|
assert(result);
|
||||||
|
assert(api.hasToken);
|
||||||
|
expect(api.baseUrl, equals("http://localhost:12345/"));
|
||||||
|
|
||||||
|
assert(api.isConnected());
|
||||||
|
assert(!api.isConnecting());
|
||||||
|
assert(api.checkConnection());
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Version Checks", () async {
|
||||||
|
// Test server version information
|
||||||
|
var api = InvenTreeAPI();
|
||||||
|
|
||||||
|
assert(await api.connectToServer());
|
||||||
|
|
||||||
|
// Check supported functions
|
||||||
|
assert(api.apiVersion >= 50);
|
||||||
|
assert(api.supportsSettings);
|
||||||
|
assert(api.supportsNotifications);
|
||||||
|
assert(api.supportsModernStockTransactions);
|
||||||
|
assert(api.supportsPoReceive);
|
||||||
|
|
||||||
|
// Check available permissions
|
||||||
|
assert(api.checkPermission("part", "change"));
|
||||||
|
assert(api.checkPermission("stocklocation", "delete"));
|
||||||
|
assert(api.checkPermission("part", "weirdpermission"));
|
||||||
|
assert(api.checkPermission("blah", "bloo"));
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
30
test/preferences_test.dart
Normal file
30
test/preferences_test.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* Unit tests for the preferences manager
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "package:test/test.dart";
|
||||||
|
import "package:inventree/preferences.dart";
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
group("Settings Tests:", () {
|
||||||
|
test("Default Values", () async {
|
||||||
|
// Boolean values
|
||||||
|
expect(await InvenTreeSettingsManager().getBool("test", false), equals(false));
|
||||||
|
expect(await InvenTreeSettingsManager().getBool("test", true), equals(true));
|
||||||
|
|
||||||
|
// String values
|
||||||
|
expect(await InvenTreeSettingsManager().getValue("test", "x"), equals("x"));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Set value", () async {
|
||||||
|
await InvenTreeSettingsManager().setValue("abc", "xyz");
|
||||||
|
|
||||||
|
expect(await InvenTreeSettingsManager().getValue("abc", "123"), equals("xyz"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
125
test/user_profile_test.dart
Normal file
125
test/user_profile_test.dart
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
* Unit tests for the API class
|
||||||
|
*/
|
||||||
|
|
||||||
|
import "package:test/test.dart";
|
||||||
|
import "package:inventree/user_profile.dart";
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
|
||||||
|
setUp(() async {
|
||||||
|
// Ensure we have a user profile available
|
||||||
|
// This profile will match the dockerized InvenTree setup, running locally
|
||||||
|
|
||||||
|
// To start with, there should not be *any* profiles available
|
||||||
|
var profiles = await UserProfileDBManager().getAllProfiles();
|
||||||
|
|
||||||
|
for (var prf in profiles) {
|
||||||
|
await UserProfileDBManager().deleteProfile(prf);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there are *no* profiles in the database
|
||||||
|
profiles = await UserProfileDBManager().getAllProfiles();
|
||||||
|
expect(profiles.length, equals(0));
|
||||||
|
|
||||||
|
// Now, create one!
|
||||||
|
bool result = await UserProfileDBManager().addProfile(UserProfile(
|
||||||
|
name: "Test Profile",
|
||||||
|
username: "testuser",
|
||||||
|
password: "testpassword""",
|
||||||
|
server: "http://localhost:12345",
|
||||||
|
selected: true,
|
||||||
|
));
|
||||||
|
|
||||||
|
expect(result, equals(true));
|
||||||
|
|
||||||
|
// Ensure we have one profile available
|
||||||
|
// expect(profiles.length, equals(1));
|
||||||
|
profiles = await UserProfileDBManager().getAllProfiles();
|
||||||
|
|
||||||
|
expect(profiles.length, equals(1));
|
||||||
|
|
||||||
|
int key = -1;
|
||||||
|
|
||||||
|
// Find the first available profile
|
||||||
|
for (var p in profiles) {
|
||||||
|
if (p.key != null) {
|
||||||
|
key = p.key ?? key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select the profile
|
||||||
|
await UserProfileDBManager().selectProfile(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run a set of tests for user profile functionality
|
||||||
|
group("Profile Tests:", () {
|
||||||
|
|
||||||
|
test("Add Invalid Profiles", () async {
|
||||||
|
// Add a profile with missing data
|
||||||
|
bool result = await UserProfileDBManager().addProfile(
|
||||||
|
UserProfile(
|
||||||
|
username: "what",
|
||||||
|
password: "why",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, equals(false));
|
||||||
|
|
||||||
|
// Add a profile with a name that already exists
|
||||||
|
result = await UserProfileDBManager().addProfile(
|
||||||
|
UserProfile(
|
||||||
|
name: "Test Profile",
|
||||||
|
username: "xyz",
|
||||||
|
password: "hunter42",
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result, equals(false));
|
||||||
|
|
||||||
|
// Check that the number of protocols available is still the same
|
||||||
|
var profiles = await UserProfileDBManager().getAllProfiles();
|
||||||
|
|
||||||
|
expect(profiles.length, equals(1));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Profile Name Check", () async {
|
||||||
|
bool result = await UserProfileDBManager().profileNameExists("doesnotexist");
|
||||||
|
expect(result, equals(false));
|
||||||
|
|
||||||
|
result = await UserProfileDBManager().profileNameExists("Test Profile");
|
||||||
|
expect(result, equals(true));
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Select Profile", () async {
|
||||||
|
// Ensure that we can select a user profile
|
||||||
|
final prf = await UserProfileDBManager().getSelectedProfile();
|
||||||
|
|
||||||
|
expect(prf, isNot(null));
|
||||||
|
|
||||||
|
if (prf != null) {
|
||||||
|
UserProfile p = prf;
|
||||||
|
|
||||||
|
expect(p.name, equals("Test Profile"));
|
||||||
|
expect(p.username, equals("testuser"));
|
||||||
|
expect(p.password, equals("testpassword"));
|
||||||
|
expect(p.server, equals("http://localhost:12345"));
|
||||||
|
|
||||||
|
expect(p.toString(), equals("<${p.key}> Test Profile : http://localhost:12345 - testuser:testpassword"));
|
||||||
|
|
||||||
|
// Test that we can update the profile
|
||||||
|
p.name = "different name";
|
||||||
|
|
||||||
|
bool result = await UserProfileDBManager().updateProfile(p);
|
||||||
|
expect(result, equals(true));
|
||||||
|
|
||||||
|
// Trying to update with an invalid value will fail!
|
||||||
|
p.password = "";
|
||||||
|
result = await UserProfileDBManager().updateProfile(p);
|
||||||
|
expect(result, equals(false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
@ -5,9 +5,9 @@
|
|||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
import "package:flutter_test/flutter_test.dart";
|
// import "package:flutter_test/flutter_test.dart";
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
testWidgets("Counter increments smoke test", (WidgetTester tester) async {
|
// testWidgets("Counter increments smoke test", (WidgetTester tester) async {
|
||||||
});
|
// });
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user