mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-11-04 07:15:46 +00:00 
			
		
		
		
	
							
								
								
									
										3
									
								
								.github/workflows/android.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.github/workflows/android.yaml
									
									
									
									
										vendored
									
									
								
							@@ -6,9 +6,6 @@ on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  
 | 
			
		||||
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:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  
 | 
			
		||||
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/*
 | 
			
		||||
 | 
			
		||||
# This file is auto-generated as part of the CI process
 | 
			
		||||
test/coverage_helper_test.dart
 | 
			
		||||
 | 
			
		||||
# Sentry API key
 | 
			
		||||
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: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";
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										189
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										189
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -1,6 +1,20 @@
 | 
			
		||||
# Generated by pub
 | 
			
		||||
# See https://dart.dev/tools/pub/glossary#lockfile
 | 
			
		||||
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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -99,6 +113,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.1"
 | 
			
		||||
  cli_util:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: cli_util
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.3.5"
 | 
			
		||||
  clock:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -113,6 +134,20 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -280,6 +315,20 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -287,6 +336,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -336,6 +392,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.17.0"
 | 
			
		||||
  io:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: io
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.3"
 | 
			
		||||
  js:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -350,6 +413,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.8.2"
 | 
			
		||||
  logging:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: logging
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.0.2"
 | 
			
		||||
  markdown:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -378,6 +448,20 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -399,6 +483,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -525,6 +616,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.2"
 | 
			
		||||
  pool:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: pool
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.5.0"
 | 
			
		||||
  process:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -532,6 +630,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -630,6 +735,34 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description: flutter
 | 
			
		||||
@@ -642,6 +775,20 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -705,6 +852,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.0"
 | 
			
		||||
  test:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
      name: test
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.19.5"
 | 
			
		||||
  test_api:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -712,6 +866,13 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -789,6 +950,34 @@ packages:
 | 
			
		||||
      url: "https://pub.dartlang.org"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    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:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								pubspec.yaml
									
									
									
									
									
								
							
							
						
						
									
										31
									
								
								pubspec.yaml
									
									
									
									
									
								
							@@ -38,10 +38,11 @@ dependencies:
 | 
			
		||||
  url_launcher: ^6.0.9                     # Open link in system browser
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_launcher_icons:
 | 
			
		||||
  flutter_launcher_icons: ^0.9.0
 | 
			
		||||
  flutter_test:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
  lint: ^1.0.0
 | 
			
		||||
  lint: ^1.8.0
 | 
			
		||||
  test: ^1.19.0
 | 
			
		||||
 | 
			
		||||
flutter_icons:
 | 
			
		||||
  android: true
 | 
			
		||||
@@ -64,29 +65,3 @@ flutter:
 | 
			
		||||
    - assets/sounds/barcode_scan.mp3
 | 
			
		||||
    - assets/sounds/barcode_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
 | 
			
		||||
// 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() {
 | 
			
		||||
  testWidgets("Counter increments smoke test", (WidgetTester tester) async {
 | 
			
		||||
  });
 | 
			
		||||
  // testWidgets("Counter increments smoke test", (WidgetTester tester) async {
 | 
			
		||||
  // });
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user