diff --git a/.envrc b/.envrc deleted file mode 100644 index a374b41..0000000 --- a/.envrc +++ /dev/null @@ -1,9 +0,0 @@ -# When nix is available, use the flake for environment management -if command -v nix &> /dev/null; then - if type use_flake &> /dev/null; then - use flake - else - echo "Falling back to nix develop (nix-direnv not available)" - eval "$(nix develop --print-env 2>/dev/null || echo 'echo Failed to load nix environment')" - fi -fi \ No newline at end of file diff --git a/.gitignore b/.gitignore index 061c296..8effbf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ # Miscellaneous -*.bak *.class *.log *.pyc @@ -7,7 +6,6 @@ .DS_Store .atom/ .buildlog/ -.direnv/ .history .svn/ diff --git a/assets/release_notes.md b/assets/release_notes.md index f38d841..72324c1 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,16 +1,9 @@ -## 0.22.1 - February 2026 +### x.xx.x - Month Year --- -- Fixes bug related to fetching images from remote URLs - -### 0.22.0 - February 2026 ---- - -- Display overall part requirements on part detail view - Support display of custom status codes - Fix default values for list sorting -- Fix bug related to null values in list filters -- Updated translations + ### 0.21.2 - January 2026 --- diff --git a/flake.lock b/flake.lock deleted file mode 100644 index e8752c2..0000000 --- a/flake.lock +++ /dev/null @@ -1,78 +0,0 @@ -{ - "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1769170682, - "narHash": "sha256-oMmN1lVQU0F0W2k6OI3bgdzp2YOHWYUAw79qzDSjenU=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "c5296fdd05cfa2c187990dd909864da9658df755", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixos-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "nixpkgs-flutter": { - "locked": { - "lastModified": 1751285371, - "narHash": "sha256-/hDU+2AUeFFu5qGHO/UyFMc4UG/x5Cw5uXO36KGTk6c=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b", - "type": "github" - }, - "original": { - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "nixpkgs-flutter": "nixpkgs-flutter" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index d1cda89..0000000 --- a/flake.nix +++ /dev/null @@ -1,113 +0,0 @@ -{ - description = "InvenTree App Development Environment"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - flake-utils.url = "github:numtide/flake-utils"; - nixpkgs-flutter.url = "github:NixOS/nixpkgs/b9c03fbbaf84d85bb28eee530c7e9edc4021ca1b"; - }; - - outputs = - { - self, - nixpkgs, - flake-utils, - nixpkgs-flutter, - }: - flake-utils.lib.eachDefaultSystem ( - system: - let - pkgs = import nixpkgs { - inherit system; - config = { - allowUnfree = true; - android_sdk.accept_license = true; - }; - overlays = [ - (final: prev: { - flutter-pkg = import (self.inputs.nixpkgs-flutter) { inherit system; }; - }) - ]; - }; - - androidSdk = - (pkgs.androidenv.composeAndroidPackages { - platformVersions = [ - "35" - "34" - ]; - buildToolsVersions = [ - "35.0.0" - "34.0.0" - ]; - includeNDK = true; - ndkVersions = [ "26.1.10909125" ]; - cmakeVersions = [ "3.22.1" ]; - }).androidsdk; - - linuxOptionals = with pkgs; [ - gtk3 - glib - pcre - libepoxy - libxkbcommon - dbus - at-spi2-core - file - ]; - - fvmScript = '' - case "$1" in - use) - echo "✓ Using Flutter from Nix ($(flutter --version | head -n1))" - exit 0 - ;; - flutter) - shift - exec flutter "$@" - ;; - dart) - shift - exec dart "$@" - ;; - *) - echo "fvm wrapper: command '$1' not implemented (using Nix-managed Flutter)" >&2 - exit 1 - ;; - esac - ''; - fvm-wrapper = pkgs.writeShellScriptBin "fvm" fvmScript; - in - { - devShells.default = pkgs.mkShell { - buildInputs = - with pkgs; - [ - flutter-pkg.flutter - fvm-wrapper - jdk17 - android-tools - gradle - python3 - python3Packages.invoke - jq - git - curl - unzip - which - ] - ++ lib.optionals stdenv.isLinux linuxOptionals; - - shellHook = '' - export ANDROID_HOME="${androidSdk}/libexec/android-sdk" - export ANDROID_SDK_ROOT="$ANDROID_HOME" - export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools/bin:$PATH" - export JAVA_HOME="${pkgs.jdk17}" - export GRADLE_OPTS="-Dorg.gradle.project.android.aapt2FromMavenOverride=$ANDROID_HOME/build-tools/35.0.0/aapt2" - ''; - - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath (with pkgs; linuxOptionals); - }; - } - ); -} diff --git a/lib/api.dart b/lib/api.dart index ab0696a..61a6379 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -163,7 +163,6 @@ class InvenTreeFileService extends FileService { * * InvenTree implements token-based authentication, which is * initialised using a username:password combination. - * Alternatively, an existing token provided by the user can be used for authentication. */ /* @@ -214,27 +213,30 @@ class InvenTreeAPI { return url; } - // Resolve a relative or absolute URL - String _makeUrl(String url, {String base = ""}) { - final baseUri = Uri.parse(base.isNotEmpty ? base : baseUrl); - final pathUri = Uri.parse(url); - - // If path is absolute (has scheme), ignore base - if (pathUri.hasScheme) { - return pathUri.toString(); + String _makeUrl(String url) { + // Strip leading slash + if (url.startsWith("/")) { + url = url.substring(1, url.length); } - return baseUri.resolveUri(pathUri).toString(); + // Prevent double-slash + url = url.replaceAll("//", "/"); + + return baseUrl + url; } + String get apiUrl => _makeUrl("/api/"); + + String get imageUrl => _makeUrl("/image/"); + String makeApiUrl(String endpoint) { - String apiBase = makeUrl("/api/"); - - return _makeUrl(endpoint, base: apiBase); + if (endpoint.startsWith("/api/") || endpoint.startsWith("api/")) { + return _makeUrl(endpoint); + } else { + return _makeUrl("/api/${endpoint}"); + } } - String get apiUrl => makeApiUrl(""); - String makeUrl(String endpoint) => _makeUrl(endpoint); UserProfile? profile; @@ -351,9 +353,6 @@ class InvenTreeAPI { // Supports separate search against "supplier" / "customer" / "manufacturer" bool get supportsSplitCompanySearch => apiVersion >= 315; - // Supports "requirements" information for specific part - bool get supportsPartRequirements => apiVersion >= 350; - // Does the server support the "modern" (consolidated) parameter API? // Ref: https://github.com/inventree/InvenTree/pull/10699 bool get supportsModernParameters => apiVersion >= 429; @@ -541,40 +540,6 @@ class InvenTreeAPI { } } - Future checkToken(UserProfile userProfile, String token) async { - debug("Checking token @ ${_URL_ME}"); - - userProfile.token = token; - profile = userProfile; - - final response = await get(_URL_ME); - - if (!response.successful()) { - switch (response.statusCode) { - case 401: - case 403: - showServerError( - apiUrl, - L10().serverAuthenticationError, - L10().invalidToken, - ); - default: - showStatusCodeError(apiUrl, response.statusCode); - } - - debug("Request failed: STATUS ${response.statusCode}"); - - // reset token - userProfile.token = ""; - profile = userProfile; - } else { - // save token - await UserProfileDBManager().updateProfile(userProfile); - } - - return response; - } - /* * Fetch a token from the server, * with a temporary authentication header @@ -1175,7 +1140,7 @@ class InvenTreeAPI { * Perform a request to link a custom barcode to a particular item */ Future linkBarcode(Map body) async { - HttpClientRequest? request = await apiRequest("barcode/link/", "POST"); + HttpClientRequest? request = await apiRequest("/barcode/link/", "POST"); if (request == null) { return false; @@ -1194,7 +1159,7 @@ class InvenTreeAPI { * Perform a request to unlink a custom barcode from a particular item */ Future unlinkBarcode(Map body) async { - HttpClientRequest? request = await apiRequest("barcode/unlink/", "POST"); + HttpClientRequest? request = await apiRequest("/barcode/unlink/", "POST"); if (request == null) { return false; diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 3510d50..033fae9 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -214,27 +214,6 @@ class InvenTreePart extends InvenTreeModel { }); } - // Request requirements information for this part - Future getRequirements() async { - try { - final response = await InvenTreeAPI().get( - "/api/part/${pk}/requirements/", - ); - if (response.isValid()) { - final requirementsData = response.data; - - if (requirementsData is Map) { - return InvenTreePartRequirements.fromJson(requirementsData); - } - } - } catch (e, stackTrace) { - print("Exception while fetching requirements data for part $pk: $e"); - sentryReportError("getRequirements", e, stackTrace); - } - - return null; - } - // Request pricing data for this part Future getPricing() async { try { @@ -458,43 +437,6 @@ class InvenTreePart extends InvenTreeModel { InvenTreePart.fromJson(json); } -/* - * Class representing requirements information for a given Part instance. - */ -class InvenTreePartRequirements extends InvenTreeModel { - InvenTreePartRequirements() : super(); - - InvenTreePartRequirements.fromJson(Map json) - : super.fromJson(json); - - @override - List get rolesRequired => ["part"]; - - @override - InvenTreeModel createFromJson(Map json) => - InvenTreePartRequirements.fromJson(json); - - // Data accessors - double get canBuild => getDouble("can_build"); - - double get ordering => getDouble("ordering"); - - double get building => getDouble("building"); - - double get scheduledToBuild => getDouble("scheduled_to_build"); - - double get requiredForBuildOrders => getDouble("required_for_build_orders"); - - double get allocatedToBuildOrders => getDouble("allocated_to_build_orders"); - - double get requiredForSalesOrders => getDouble("required_for_sales_orders"); - - double get allocatedToSalesOrders => getDouble("allocated_to_sales_orders"); -} - -/* - * Class representing pricing information for a given Part instance. - */ class InvenTreePartPricing extends InvenTreeModel { InvenTreePartPricing() : super(); diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 8ff680a..657a218 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -534,19 +534,19 @@ class InvenTreeStockItem extends InvenTreeModel { } Future countStock(double q, {String? notes}) async { - final bool result = await adjustStock("stock/count/", q, notes: notes); + final bool result = await adjustStock("/stock/count/", q, notes: notes); return result; } Future addStock(double q, {String? notes}) async { - final bool result = await adjustStock("stock/add/", q, notes: notes); + final bool result = await adjustStock("/stock/add/", q, notes: notes); return result; } Future removeStock(double q, {String? notes}) async { - final bool result = await adjustStock("stock/remove/", q, notes: notes); + final bool result = await adjustStock("/stock/remove/", q, notes: notes); return result; } @@ -563,7 +563,7 @@ class InvenTreeStockItem extends InvenTreeModel { } final bool result = await adjustStock( - "stock/transfer/", + "/stock/transfer/", q, notes: notes, location: location, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 98e9122..4f6560f 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -68,12 +68,6 @@ "allocatedStock": "Allocated Stock", "@allocatedStock": {}, - "allocatedToBuildOrders": "Allocated to Build Orders", - "@allocatedToBuildOrders": {}, - - "allocatedToSalesOrders": "Allocated to Sales Orders", - "@allocatedToSalesOrders": {}, - "appReleaseNotes": "Display app release notes", "@appReleaseNotes": {}, @@ -232,12 +226,6 @@ "cameraInternalDetail": "Use internal camera to read barcodes", "@cameraInternalDetail": {}, - "canBuild": "Can Build", - "@canBuild": {}, - - "canBuildDetail": "Can be produced with current stock", - "@canBuildDetail": {}, - "cancel": "Cancel", "@cancel": { "description": "Cancel" @@ -430,9 +418,6 @@ "enterPassword": "Enter password", "@enterPassword": {}, - "enterToken": "Enter API token", - "@enterToken": {}, - "enterUsername": "Enter username", "@enterUsername": {}, @@ -680,9 +665,6 @@ "invalidSupplierPart": "Invalid Supplier Part", "@invalidSupplierPart": {}, - "invalidToken": "Invalid token", - "@invalidToken": {}, - "invalidUsernamePassword": "Invalid username / password combination", "@invalidUsernamePassword": {}, @@ -788,9 +770,6 @@ "login": "Login", "@login": {}, - "loginMethod": "Login method", - "@loginMethod": {}, - "loginEnter": "Enter login details", "@loginEnter": {}, @@ -957,12 +936,6 @@ "partPricingSettingDetail": "Display part pricing information", "@pricingSettingDetail": {}, - "partRequirements": "Part Requirements", - "@partRequirements": {}, - - "partRequirementsSettingDetail": "Display part requirements", - "@partRequirementsSettingDetail": {}, - "partSettings": "Part Settings", "@partSettings": {}, @@ -1562,9 +1535,6 @@ "stockLocations": "Stock Locations", "@stockLocations": {}, - "stockSettings": "Stock Settings", - "@stockSettings": {}, - "stockTopLevel": "Top level stock location", "@stockTopLevel": {}, @@ -1674,12 +1644,6 @@ "toggleTorch": "Toggle Torch", "@toggleTorch": {}, - "token": "Token", - "@token": {}, - - "tokenEmpty": "Token cannot be empty", - "@tokenEmpty": {}, - "tokenError": "Token Error", "@tokenError": {}, @@ -1756,9 +1720,6 @@ "username": "Username", "@username": {}, - "usernameAndPassword": "Username & Password", - "@usernameAndPassword": {}, - "usernameEmpty": "Username cannot be empty", "@usernameEmpty": {}, diff --git a/lib/preferences.dart b/lib/preferences.dart index a4bc05d..92eda1f 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -34,7 +34,6 @@ const String INV_LABEL_DEFAULT_PLUGIN = "defaultLabelPlugin"; // Part settings const String INV_PART_SHOW_BOM = "partShowBom"; const String INV_PART_SHOW_PRICING = "partShowPricing"; -const String INV_PART_SHOW_REQUIREMENTS = "partShowRequirements"; // Stock settings const String INV_STOCK_SHOW_HISTORY = "stockShowHistory"; diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 95d11f1..2e04d49 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -8,8 +8,6 @@ import "package:inventree/api.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/progress.dart"; -enum LoginMethod { credentials, token } - class InvenTreeLoginWidget extends StatefulWidget { const InvenTreeLoginWidget(this.profile) : super(); @@ -24,12 +22,9 @@ class _InvenTreeLoginState extends State { String username = ""; String password = ""; - String token = ""; bool _obscured = true; - LoginMethod _method = LoginMethod.credentials; - String error = ""; // Attempt login @@ -49,19 +44,12 @@ class _InvenTreeLoginState extends State { showLoadingOverlay(); - final APIResponse response; - - if (_method == LoginMethod.credentials) { - // Attempt login - response = await InvenTreeAPI().fetchToken( - widget.profile, - username, - password, - ); - } else { - // Check token validity - response = await InvenTreeAPI().checkToken(widget.profile, token); - } + // Attempt login + final response = await InvenTreeAPI().fetchToken( + widget.profile, + username, + password, + ); hideLoadingOverlay(); @@ -135,120 +123,55 @@ class _InvenTreeLoginState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ ...before, - // Dropdown to select login method - Padding( - padding: EdgeInsets.symmetric(vertical: 8), - child: DropdownButtonFormField( - value: _method, - decoration: InputDecoration( - labelText: L10().loginMethod, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - ), - items: [ - DropdownMenuItem( - child: Text(L10().usernameAndPassword), - value: LoginMethod.credentials, - ), - DropdownMenuItem( - child: Text(L10().token), - value: LoginMethod.token, - ), - ], - onChanged: (val) { - setState(() { - _method = val ?? LoginMethod.credentials; - error = ""; - }); - }, + TextFormField( + decoration: InputDecoration( + labelText: L10().username, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + hintText: L10().enterUsername, ), + initialValue: "", + keyboardType: TextInputType.text, + onSaved: (value) { + username = value?.trim() ?? ""; + }, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return L10().usernameEmpty; + } + + return null; + }, ), - - // Show fields depending on selected login method - if (_method == LoginMethod.credentials) ...[ - TextFormField( - key: ValueKey("input-username"), - decoration: InputDecoration( - labelText: L10().username, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - hintText: L10().enterUsername, + TextFormField( + decoration: InputDecoration( + labelText: L10().password, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + hintText: L10().enterPassword, + suffixIcon: IconButton( + icon: _obscured + ? Icon(TablerIcons.eye) + : Icon(TablerIcons.eye_off), + onPressed: () { + setState(() { + _obscured = !_obscured; + }); + }, ), - initialValue: "", - keyboardType: TextInputType.text, - onSaved: (value) { - username = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().usernameEmpty; - } - - return null; - }, ), - TextFormField( - key: ValueKey("input-password"), - decoration: InputDecoration( - labelText: L10().password, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - hintText: L10().enterPassword, - suffixIcon: IconButton( - icon: _obscured - ? Icon(TablerIcons.eye) - : Icon(TablerIcons.eye_off), - onPressed: () { - setState(() { - _obscured = !_obscured; - }); - }, - ), - ), - initialValue: "", - keyboardType: TextInputType.visiblePassword, - obscureText: _obscured, - onSaved: (value) { - password = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().passwordEmpty; - } + initialValue: "", + keyboardType: TextInputType.visiblePassword, + obscureText: _obscured, + onSaved: (value) { + password = value?.trim() ?? ""; + }, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return L10().passwordEmpty; + } - return null; - }, - ), - ] else ...[ - TextFormField( - key: ValueKey("input-token"), - decoration: InputDecoration( - labelText: L10().token, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - hintText: L10().enterToken, - suffixIcon: IconButton( - icon: _obscured - ? Icon(TablerIcons.eye) - : Icon(TablerIcons.eye_off), - onPressed: () { - setState(() { - _obscured = !_obscured; - }); - }, - ), - ), - initialValue: "", - keyboardType: TextInputType.visiblePassword, - obscureText: _obscured, - onSaved: (value) { - token = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().tokenEmpty; - } - - return null; - }, - ), - ], + return null; + }, + ), ...after, ], ), diff --git a/lib/settings/part_settings.dart b/lib/settings/part_settings.dart index fdb5c45..aa13678 100644 --- a/lib/settings/part_settings.dart +++ b/lib/settings/part_settings.dart @@ -15,7 +15,9 @@ class _InvenTreePartSettingsState extends State { bool partShowBom = true; bool partShowPricing = true; - bool partShowRequirements = false; + bool stockShowHistory = false; + bool stockShowTests = false; + bool stockConfirmScan = false; @override void initState() { @@ -33,8 +35,16 @@ class _InvenTreePartSettingsState extends State { INV_PART_SHOW_PRICING, true, ); - partShowRequirements = await InvenTreeSettingsManager().getBool( - INV_PART_SHOW_REQUIREMENTS, + stockShowHistory = await InvenTreeSettingsManager().getBool( + INV_STOCK_SHOW_HISTORY, + false, + ); + stockShowTests = await InvenTreeSettingsManager().getBool( + INV_STOCK_SHOW_TESTS, + true, + ); + stockConfirmScan = await InvenTreeSettingsManager().getBool( + INV_STOCK_CONFIRM_SCAN, false, ); @@ -84,19 +94,54 @@ class _InvenTreePartSettingsState extends State { }, ), ), + Divider(), ListTile( - title: Text(L10().partRequirements), - subtitle: Text(L10().partRequirementsSettingDetail), - leading: Icon(TablerIcons.list), + title: Text(L10().stockItemHistory), + subtitle: Text(L10().stockItemHistoryDetail), + leading: Icon(TablerIcons.history), trailing: Switch( - value: partShowRequirements, + value: stockShowHistory, onChanged: (bool value) { InvenTreeSettingsManager().setValue( - INV_PART_SHOW_REQUIREMENTS, + INV_STOCK_SHOW_HISTORY, value, ); setState(() { - partShowRequirements = value; + stockShowHistory = value; + }); + }, + ), + ), + ListTile( + title: Text(L10().testResults), + subtitle: Text(L10().testResultsDetail), + leading: Icon(TablerIcons.test_pipe), + trailing: Switch( + value: stockShowTests, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue( + INV_STOCK_SHOW_TESTS, + value, + ); + setState(() { + stockShowTests = value; + }); + }, + ), + ), + ListTile( + title: Text(L10().confirmScan), + subtitle: Text(L10().confirmScanDetail), + leading: Icon(TablerIcons.qrcode), + trailing: Switch( + value: stockConfirmScan, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue( + INV_STOCK_CONFIRM_SCAN, + value, + ); + setState(() { + stockConfirmScan = value; }); }, ), diff --git a/lib/settings/settings.dart b/lib/settings/settings.dart index 15cd37a..3815127 100644 --- a/lib/settings/settings.dart +++ b/lib/settings/settings.dart @@ -1,6 +1,5 @@ import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; -import "package:inventree/settings/stock_settings.dart"; import "package:package_info_plus/package_info_plus.dart"; import "package:inventree/app_colors.dart"; @@ -120,20 +119,6 @@ class _InvenTreeSettingsState extends State { ); }, ), - ListTile( - title: Text(L10().stock), - subtitle: Text(L10().stockSettings), - leading: Icon(TablerIcons.packages, color: COLOR_ACTION), - trailing: LinkIcon(), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => InvenTreeStockSettingsWidget(), - ), - ); - }, - ), ListTile( title: Text(L10().purchaseOrder), subtitle: Text(L10().purchaseOrderSettings), diff --git a/lib/settings/stock_settings.dart b/lib/settings/stock_settings.dart deleted file mode 100644 index 3856d23..0000000 --- a/lib/settings/stock_settings.dart +++ /dev/null @@ -1,110 +0,0 @@ -import "package:flutter/material.dart"; -import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; -import "package:inventree/app_colors.dart"; -import "package:inventree/l10.dart"; -import "package:inventree/preferences.dart"; - -class InvenTreeStockSettingsWidget extends StatefulWidget { - @override - _InvenTreeStockSettingsState createState() => _InvenTreeStockSettingsState(); -} - -class _InvenTreeStockSettingsState extends State { - _InvenTreeStockSettingsState(); - - bool stockShowHistory = false; - bool stockShowTests = false; - bool stockConfirmScan = false; - - @override - void initState() { - super.initState(); - loadSettings(); - } - - Future loadSettings() async { - stockShowHistory = await InvenTreeSettingsManager().getBool( - INV_STOCK_SHOW_HISTORY, - false, - ); - stockShowTests = await InvenTreeSettingsManager().getBool( - INV_STOCK_SHOW_TESTS, - true, - ); - stockConfirmScan = await InvenTreeSettingsManager().getBool( - INV_STOCK_CONFIRM_SCAN, - false, - ); - - if (mounted) { - setState(() {}); - } - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(L10().stockSettings), - backgroundColor: COLOR_APP_BAR, - ), - body: Container( - child: ListView( - children: [ - ListTile( - title: Text(L10().stockItemHistory), - subtitle: Text(L10().stockItemHistoryDetail), - leading: Icon(TablerIcons.history), - trailing: Switch( - value: stockShowHistory, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue( - INV_STOCK_SHOW_HISTORY, - value, - ); - setState(() { - stockShowHistory = value; - }); - }, - ), - ), - ListTile( - title: Text(L10().testResults), - subtitle: Text(L10().testResultsDetail), - leading: Icon(TablerIcons.test_pipe), - trailing: Switch( - value: stockShowTests, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue( - INV_STOCK_SHOW_TESTS, - value, - ); - setState(() { - stockShowTests = value; - }); - }, - ), - ), - ListTile( - title: Text(L10().confirmScan), - subtitle: Text(L10().confirmScanDetail), - leading: Icon(TablerIcons.qrcode), - trailing: Switch( - value: stockConfirmScan, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue( - INV_STOCK_CONFIRM_SCAN, - value, - ); - setState(() { - stockConfirmScan = value; - }); - }, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 6bbf355..123c7ab 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -61,14 +61,6 @@ abstract class PaginatedSearchState backup, ); - if (result == "null") { - if (tristate) { - return null; - } else { - return backup; - } - } - return result; } @@ -77,7 +69,7 @@ abstract class PaginatedSearchState final String settings_key = "${prefix}filter_${key}"; if (value == null) { - await InvenTreeSettingsManager().setValue(settings_key, "null"); + await InvenTreeSettingsManager().removeValue(settings_key); } else { await InvenTreeSettingsManager().setValue(settings_key, value); } diff --git a/lib/widget/part/bom_list.dart b/lib/widget/part/bom_list.dart index 0e87506..5e17e53 100644 --- a/lib/widget/part/bom_list.dart +++ b/lib/widget/part/bom_list.dart @@ -123,22 +123,10 @@ class _PaginatedBomListState extends PaginatedSearchState { @override Map> get filterOptions => { - "sub_part_active": { - "label": L10().filterActive, - "help_text": L10().filterActiveDetail, - "tristate": true, - "default": true, - }, "sub_part_assembly": { "label": L10().filterAssembly, "help_text": L10().filterAssemblyDetail, }, - "sub_part_virtual": { - "label": L10().filterVirtual, - "help_text": L10().filterVirtualDetail, - "tristate": true, - "default": true, - }, }; @override diff --git a/lib/widget/part/part_detail.dart b/lib/widget/part/part_detail.dart index 08c6df5..73a299f 100644 --- a/lib/widget/part/part_detail.dart +++ b/lib/widget/part/part_detail.dart @@ -54,7 +54,6 @@ class _PartDisplayState extends RefreshableState { bool allowLabelPrinting = false; bool showBom = false; bool showPricing = false; - bool showRequirements = false; int parameterCount = 0; int attachmentCount = 0; @@ -63,7 +62,6 @@ class _PartDisplayState extends RefreshableState { int variantCount = 0; InvenTreePartPricing? partPricing; - InvenTreePartRequirements? partRequirements; @override String getAppBarTitle() => L10().partDetails; @@ -150,12 +148,6 @@ class _PartDisplayState extends RefreshableState { final bool result = await part.reload(); // Load page settings from local storage - - showRequirements = await InvenTreeSettingsManager().getBool( - INV_PART_SHOW_REQUIREMENTS, - false, - ); - showPricing = await InvenTreeSettingsManager().getBool( INV_PART_SHOW_PRICING, true, @@ -241,23 +233,6 @@ class _PartDisplayState extends RefreshableState { }); } - // If show requirements information? - if (showRequirements && api.supportsPartRequirements) { - part.getRequirements().then((InvenTreePartRequirements? requirements) { - if (mounted) { - setState(() { - partRequirements = requirements; - }); - } - }); - } else { - if (mounted) { - setState(() { - partRequirements = null; - }); - } - } - // If show pricing information? if (showPricing) { part.getPricing().then((InvenTreePartPricing? pricing) { @@ -267,12 +242,6 @@ class _PartDisplayState extends RefreshableState { }); } }); - } else { - if (mounted) { - setState(() { - partPricing = null; - }); - } } // Request the number of BOM items @@ -465,103 +434,6 @@ class _PartDisplayState extends RefreshableState { ); } - // Part "requirements" - if (showRequirements && - api.supportsPartRequirements && - partRequirements != null) { - // Assembly parts - if (part.isAssembly) { - // Scheduled to build - if (partRequirements!.building > 0 || - partRequirements!.scheduledToBuild > 0) { - tiles.add( - ListTile( - title: Text(L10().building), - subtitle: ProgressBar( - partRequirements!.building, - maximum: partRequirements!.scheduledToBuild, - ), - leading: Icon(TablerIcons.tools), - trailing: ProgressText( - partRequirements!.building, - maximum: partRequirements!.scheduledToBuild, - ), - ), - ); - } - - // Can build - if (part.isActive) { - tiles.add( - ListTile( - title: Text(L10().canBuild), - subtitle: Text(L10().canBuildDetail), - trailing: LargeText( - simpleNumberString(partRequirements!.canBuild), - ), - leading: Icon(TablerIcons.check), - ), - ); - } - } - - // Build requirements - if (partRequirements!.requiredForBuildOrders > 0 || - partRequirements!.allocatedToBuildOrders > 0) { - tiles.add( - ListTile( - title: Text(L10().allocatedToBuildOrders), - subtitle: ProgressBar( - partRequirements!.allocatedToBuildOrders, - maximum: partRequirements!.requiredForBuildOrders, - ), - trailing: ProgressText( - partRequirements!.allocatedToBuildOrders, - maximum: partRequirements!.requiredForBuildOrders, - ), - leading: Icon(TablerIcons.tools), - ), - ); - } - - // Sales requirements - if (part.isSalable) { - if (partRequirements!.requiredForSalesOrders > 0 || - partRequirements!.allocatedToSalesOrders > 0) { - tiles.add( - ListTile( - title: Text(L10().allocatedToSalesOrders), - subtitle: ProgressBar( - partRequirements!.allocatedToSalesOrders, - maximum: partRequirements!.requiredForSalesOrders, - ), - trailing: ProgressText( - partRequirements!.allocatedToSalesOrders, - maximum: partRequirements!.requiredForSalesOrders, - ), - leading: Icon(TablerIcons.truck_delivery), - ), - ); - } - } - - // Ordering stats - if (part.isPurchaseable && partRequirements!.ordering > 0) { - // On order - tiles.add( - ListTile( - title: Text(L10().onOrder), - subtitle: Text(L10().onOrderDetails), - leading: Icon(TablerIcons.shopping_cart), - trailing: LargeText("${part.onOrderString}"), - onTap: () { - // TODO - Order views - }, - ), - ); - } - } - if (showPricing && partPricing != null) { String pricing = formatPriceRange( partPricing?.overallMin, @@ -590,6 +462,22 @@ class _PartDisplayState extends RefreshableState { ); } + // Tiles for "purchaseable" parts + if (part.isPurchaseable) { + // On order + tiles.add( + ListTile( + title: Text(L10().onOrder), + subtitle: Text(L10().onOrderDetails), + leading: Icon(TablerIcons.shopping_cart), + trailing: LargeText("${part.onOrderString}"), + onTap: () { + // TODO - Order views + }, + ), + ); + } + // Tiles for an "assembly" part if (part.isAssembly) { if (showBom && bomCount > 0) { diff --git a/lib/widget/progress.dart b/lib/widget/progress.dart index bb043ad..a95ace9 100644 --- a/lib/widget/progress.dart +++ b/lib/widget/progress.dart @@ -3,8 +3,6 @@ import "dart:io"; import "package:flutter/material.dart"; import "package:flutter_overlay_loader/flutter_overlay_loader.dart"; import "package:inventree/app_colors.dart"; -import "package:inventree/helpers.dart"; -import "package:inventree/widget/link_icon.dart"; import "package:one_context/one_context.dart"; /* @@ -27,15 +25,6 @@ Widget ProgressBar(double value, {double maximum = 1.0}) { ); } -Widget ProgressText(double value, {double maximum = 1.0}) { - Color textColor = value < maximum ? COLOR_WARNING : COLOR_SUCCESS; - - String v = simpleNumberString(value); - String m = simpleNumberString(maximum); - - return LargeText("${v} / ${m}", color: textColor); -} - /* * Construct a circular progress indicator */ diff --git a/pubspec.yaml b/pubspec.yaml index 14f931f..129d635 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: inventree description: InvenTree stock management -version: 0.22.1+110 +version: 0.21.2+108 environment: sdk: ^3.8.1 diff --git a/test/api_test.dart b/test/api_test.dart index 3bd5cdb..f19dbf4 100644 --- a/test/api_test.dart +++ b/test/api_test.dart @@ -23,27 +23,6 @@ void main() { assert(await UserProfileDBManager().selectProfileByName(testServerName)); }); - // Ensure that generated URLs are correct - group("URL Tests:", () { - test("Generate URLs", () async { - UserProfile profile = await setupServerProfile(); - var api = InvenTreeAPI(); - - api.profile = profile; - - Map tests = { - "": "http://localhost:8000/api/", - "barcode/": "http://localhost:8000/api/barcode/", - "https://remote-server.com/media/image.png": - "https://remote-server.com/media/image.png", - }; - - for (var test in tests.entries) { - expect(api.makeApiUrl(test.key), equals(test.value)); - } - }); - }); - group("Login Tests:", () { test("Disconnected", () async { // Test that calling disconnect() does the right thing diff --git a/update-flutter-nix.sh b/update-flutter-nix.sh deleted file mode 100755 index 1f60593..0000000 --- a/update-flutter-nix.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env bash - -# This script adjusts flake.nix to use the flutter version -# pinned in .fvmrc. It fetches the appropriate commit hash. - -set -euo pipefail - -for cmd in jq curl sed nix; do - command -v $cmd >/dev/null 2>&1 || { echo "Error: $cmd is not installed!"; exit 1; } -done - -if [ ! -f .fvmrc ]; then - echo ".fvmrc file not found!" - exit 1 -fi - -FLUTTER_VERSION=$(jq -r .flutter .fvmrc) -echo "Requested Flutter version: $FLUTTER_VERSION" - -API_URL="https://search.devbox.sh/v2/pkg?name=flutter" -RELEASES_JSON=$(curl -s "$API_URL") - -ALL_VERSIONS=$(echo "$RELEASES_JSON" | jq -r '.releases[].version' | sort -V) - -FOUND_VERSION=$(echo "$ALL_VERSIONS" | awk -v v="$FLUTTER_VERSION" '$0 >= v { print; exit }') - -if [ -z "$FOUND_VERSION" ]; then - echo "Error: No matching Flutter version found." - exit 1 -fi - -if [ "$FOUND_VERSION" != "$FLUTTER_VERSION" ]; then - echo "Note: Exact version not found, using version $FOUND_VERSION instead." -fi - -COMMIT=$(echo "$RELEASES_JSON" | jq -r --arg v "$FOUND_VERSION" '.releases[] | select(.version==$v) | .platforms[] | select(.system=="x86_64-linux") | .commit_hash' | head -n 1) - -if [ -z "$COMMIT" ] || [ "$COMMIT" == "null" ]; then - echo "Error: No commit hash found for version $FOUND_VERSION and platform x86_64-linux." - exit 1 -fi - -echo "Found commit: $COMMIT (Version: $FOUND_VERSION)" - - -sed -i.bak "s|nixpkgs-flutter.url = \"github:NixOS/nixpkgs/[a-f0-9]*\";|nixpkgs-flutter.url = \"github:NixOS/nixpkgs/$COMMIT\";|" flake.nix - -nix flake update nixpkgs-flutter - -if command -v direnv >/dev/null 2>&1; then - direnv reload -fi - -echo "Success! flake.nix now uses the commit for Flutter $FLUTTER_VERSION (or higher)."