diff --git a/README.md b/README.md index 866e6f0..a2fdfe3 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Download and install from the [Apple App Store](https://apps.apple.com/au/app/in ### Direct Download (Android) -We provide direct downloads for Android users - view our [download page via polar.sh](https://buy.polar.sh/polar_cl_UnGILJ0c7P3hQrOrJs127oyLTTDOTHKrnqfCg30XtBI) +We provide direct downloads for Android users - view our [download page via polar.sh](https://polar.sh/inventree/products/299bf0d5-af88-4e0f-becf-c007ad37ecf2) ## User Documentation diff --git a/android/app/build.gradle b/android/app/build.gradle index 71834ec..d133d64 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -30,8 +30,7 @@ if (keystorePropertiesFile.exists()) { android { namespace "inventree.inventree_app" - ndkVersion "26.3.11579264" - compileSdkVersion 36 + compileSdkVersion 35 compileOptions { sourceCompatibility JavaVersion.VERSION_17 @@ -57,7 +56,7 @@ android { defaultConfig { applicationId "inventree.inventree_app" - minSdkVersion 23 + minSdkVersion 21 targetSdkVersion 35 versionCode flutterVersionCode.toInteger() versionName flutterVersionName diff --git a/android/settings.gradle b/android/settings.gradle index d8b786c..eb1096e 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "com.android.application" version "8.6.0" apply false - id "org.jetbrains.kotlin.android" version "2.3.10" apply false + id "org.jetbrains.kotlin.android" version "1.9.25" apply false } include ":app" \ No newline at end of file diff --git a/assets/release_notes.md b/assets/release_notes.md index e5e9169..f38d841 100644 --- a/assets/release_notes.md +++ b/assets/release_notes.md @@ -1,39 +1,3 @@ -## 0.22.7 - March 2026 ---- - -- Bug fix for loading sales order shipments - -## 0.22.6 - March 2026 ---- - -- Fix for displaying stock item test results - -## 0.22.5 - March 2026 ---- - -- Fix default value for "cascade" filter in PartCategory list view -- Fix default value for "cascade" filter in StockLocation list view - -## 0.22.4 - March 2026 ---- - -- Adds button to check server connection -- Fixes bug fetching sales order shipments -- Fix for boolean fields in API forms - -## 0.22.3 - February 2026 ---- - -- Auto-fill location data when receiving item via barcode scan -- Visual improvements for boolean form fields -- Add support for tri-state boolean form fields -- Bug fixes for refreshing list view data - -## 0.22.2 - February 2026 ---- - -- Bug fix for label printing, which used improperly formatted URL - ## 0.22.1 - February 2026 --- diff --git a/flake.lock b/flake.lock index 7746b8d..e8752c2 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1774386573, - "narHash": "sha256-4hAV26quOxdC6iyG7kYaZcM3VOskcPUrdCQd/nx8obc=", + "lastModified": 1769170682, + "narHash": "sha256-oMmN1lVQU0F0W2k6OI3bgdzp2YOHWYUAw79qzDSjenU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "46db2e09e1d3f113a13c0d7b81e2f221c63b8ce9", + "rev": "c5296fdd05cfa2c187990dd909864da9658df755", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 078aaaf..d1cda89 100644 --- a/flake.nix +++ b/flake.nix @@ -33,17 +33,15 @@ androidSdk = (pkgs.androidenv.composeAndroidPackages { platformVersions = [ - "36" "35" "34" ]; buildToolsVersions = [ - "36.0.0" "35.0.0" "34.0.0" ]; includeNDK = true; - ndkVersions = [ "26.3.11579264" ]; + ndkVersions = [ "26.1.10909125" ]; cmakeVersions = [ "3.22.1" ]; }).androidsdk; diff --git a/ios/.gitignore b/ios/.gitignore index c888ad8..0ca5a97 100644 --- a/ios/.gitignore +++ b/ios/.gitignore @@ -22,7 +22,6 @@ Flutter/Generated.xcconfig Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip -Flutter/ephemeral/ Flutter/flutter_assets/ Flutter/flutter_export_environment.sh ServiceDefinitions.json diff --git a/lib/api.dart b/lib/api.dart index bd4d1ee..ab0696a 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -411,7 +411,7 @@ class InvenTreeAPI { * 5. Request information on available plugins */ Future _connectToServer() async { - if (!await checkServer()) { + if (!await _checkServer()) { return false; } @@ -451,8 +451,8 @@ class InvenTreeAPI { * Check that the remote server is available. * Ping the api/ endpoint, which does not require user authentication */ - Future checkServer({String? server}) async { - String address = server ?? profile?.server ?? ""; + Future _checkServer() async { + String address = profile?.server ?? ""; if (address.isEmpty) { showSnackIcon( @@ -463,10 +463,8 @@ class InvenTreeAPI { return false; } - String url = _makeUrl("/api/", base: address); - - if (!url.endsWith("/")) { - url = url + "/"; + if (!address.endsWith("/")) { + address = address + "/"; } // Cache the "strictHttps" setting, so we can use it later without async requirement @@ -476,7 +474,7 @@ class InvenTreeAPI { debug("Connecting to ${apiUrl}"); - APIResponse response = await get(url, expectedStatusCode: 200); + APIResponse response = await get("", expectedStatusCode: 200); if (!response.successful()) { debug("Server returned invalid response: ${response.statusCode}"); diff --git a/lib/api_form.dart b/lib/api_form.dart index d826898..9df134c 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -275,7 +275,7 @@ class APIFormField { return; } - String url = api_url + value.toString() + "/"; + String url = api_url + "/" + value.toString() + "/"; final APIResponse response = await InvenTreeAPI().get(url, params: filters); @@ -293,8 +293,6 @@ class APIFormField { return _constructString(); case "boolean": return _constructBoolean(); - case "boolean filter": - return _constructBooleanFilter(); case "related field": return _constructRelatedField(); case "integer": @@ -876,109 +874,25 @@ class APIFormField { // Construct a boolean input element Widget _constructBoolean() { - bool v = false; + bool? initial_value; - if (value is bool) { - v = value as bool; + if (value is bool || value == null) { + initial_value = value as bool?; } else { - v = false; + String vs = value.toString().toLowerCase(); + initial_value = ["1", "true", "yes"].contains(vs); } - return ListTile( - title: Text(label), - subtitle: Text(helpText), - contentPadding: EdgeInsets.zero, - trailing: Switch( - value: v, - onChanged: (val) { - setFieldValue(val); - }, - ), - ); - } - - // Construct a tri-state boolean filter element - Widget _constructBooleanFilter() { - String initial_value = "null"; - - bool allow_null = (getParameter("tristate") ?? false) as bool; - - if (value is bool) { - initial_value = value.toString().toLowerCase(); - } else if (value == null) { - if (allow_null) { - initial_value = "null"; - } else { - initial_value = "false"; - } - } else { - // Not a boolean value - may be a string - if (["1", "true", "yes"].contains(value.toString().toLowerCase())) { - initial_value = "true"; - } else if ([ - "0", - "false", - "no", - ].contains(value.toString().toLowerCase())) { - initial_value = "false"; - } else if (allow_null) { - initial_value = "null"; - } else { - initial_value = "false"; - } - } - - List> buttons = []; - - if ((getParameter("tristate") ?? false) as bool) { - buttons.add( - ButtonSegment( - value: "null", - icon: Icon(TablerIcons.minus, color: COLOR_GRAY_LIGHT), - ), - ); - } - - buttons.add( - ButtonSegment( - value: "false", - icon: Icon(TablerIcons.x, color: COLOR_DANGER), - ), - ); - - buttons.add( - ButtonSegment( - value: "true", - icon: Icon(TablerIcons.check, color: COLOR_SUCCESS), - ), - ); - - return ListTile( - title: Text(label), - subtitle: Text(helpText), - contentPadding: EdgeInsets.zero, - trailing: SegmentedButton( - segments: buttons, - selected: {initial_value}, - showSelectedIcon: false, - multiSelectionEnabled: false, - style: SegmentedButton.styleFrom( - padding: EdgeInsets.all(0), - // minimumSize: MaterialStateProperty.all(Size(0, 0)), - // tapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: VisualDensity.compact, - ), - onSelectionChanged: (Set selection) { - String element = selection.first; - if (element == "null" && allow_null) { - setFieldValue(null); - } else if (element == "true") { - setFieldValue(true); - } else { - setFieldValue(false); - } - }, - ), + return CheckBoxField( + label: label, + labelStyle: _labelStyle(), + helperText: helpText, + helperStyle: _helperStyle(), + initial: initial_value, + tristate: (getParameter("tristate") ?? false) as bool, + onSaved: (val) { + setFieldValue(val); + }, ); } @@ -1254,9 +1168,7 @@ class APIFormWidgetState extends State { // Callback for when a field value is changed // Default implementation does nothing, // but custom form implementations may override this function - void onValueChanged(String field, dynamic value) { - setState(() {}); - } + void onValueChanged(String field, dynamic value) {} Future handleSuccess( Map submittedData, @@ -1482,7 +1394,7 @@ class APIFormWidgetState extends State { if (field.isSimple) { // Simple top-level field data - data[field.name] = field.data["value"] ?? field.defaultValue; + data[field.name] = field.data["value"]; } else { // Not so simple... (WHY DID I MAKE THE API SO COMPLEX?) if (field.parent.isNotEmpty) { diff --git a/lib/barcode/purchase_order.dart b/lib/barcode/purchase_order.dart index 3e2cdba..65f4913 100644 --- a/lib/barcode/purchase_order.dart +++ b/lib/barcode/purchase_order.dart @@ -154,12 +154,9 @@ class POAllocateBarcodeHandler extends BarcodeHandler { return onBarcodeUnknown(data); } - // Extract field data from the returned result dynamic supplier_part = data["supplierpart"]; - dynamic location = data["location"]; int supplier_part_pk = -1; - int location_pk = -1; if (supplier_part is Map) { supplier_part_pk = (supplier_part["pk"] ?? -1) as int; @@ -167,10 +164,6 @@ class POAllocateBarcodeHandler extends BarcodeHandler { return onBarcodeUnknown(data); } - if (location is Map) { - location_pk = (location["pk"] ?? -1) as int; - } - // Dispose of the barcode scanner if (OneContext.hasContext) { OneContext().pop(); @@ -184,10 +177,6 @@ class POAllocateBarcodeHandler extends BarcodeHandler { fields["part"]?["hidden"] = false; fields["part"]?["value"] = supplier_part_pk; - if (location_pk > 0) { - fields["location"]?["value"] = location_pk; - } - InvenTreePOLineItem().createForm( context, L10().lineItemAdd, diff --git a/lib/inventree/sales_order.dart b/lib/inventree/sales_order.dart index 9e239a7..82cc074 100644 --- a/lib/inventree/sales_order.dart +++ b/lib/inventree/sales_order.dart @@ -270,9 +270,9 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel { InvenTreeSalesOrderShipment.fromJson(json); @override - String get URL => "order/so/shipment/"; + String get URL => "/order/so/shipment/"; - String get SHIP_SHIPMENT_URL => "order/so/shipment/${pk}/ship/"; + String get SHIP_SHIPMENT_URL => "/order/so/shipment/${pk}/ship/"; @override Future goToDetailPage(BuildContext context) async { @@ -345,7 +345,7 @@ class InvenTreeSalesOrderAllocation extends InvenTreeModel { InvenTreeSalesOrderAllocation.fromJson(json); @override - String get URL => "order/so-allocation/"; + String get URL => "/order/so-allocation/"; @override List get rolesRequired => ["sales_order"]; diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index b69d018..98e9122 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -294,9 +294,6 @@ "confirmScanDetail": "Confirm stock transfer details when scanning barcodes", "@confirmScanDetail": {}, - "connectionCheck": "Check Connection", - "@connectionCheck": {}, - "connectionRefused": "Connection Refused", "@connectionRefused": {}, diff --git a/lib/labels.dart b/lib/labels.dart index 8743a4a..2b7cc27 100644 --- a/lib/labels.dart +++ b/lib/labels.dart @@ -7,7 +7,7 @@ import "package:inventree/l10.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; -const String PRINT_LABEL_URL = "label/print/"; +const String PRINT_LABEL_URL = "api/label/print/"; /* * Custom form handler for label printing. @@ -45,8 +45,6 @@ class LabelFormWidgetState extends APIFormWidgetState { if (field == "plugin") { onPluginChanged(value.toString()); } - - super.onValueChanged(field, value); } @override diff --git a/lib/settings/select_server.dart b/lib/settings/select_server.dart index 1d37855..bfeae98 100644 --- a/lib/settings/select_server.dart +++ b/lib/settings/select_server.dart @@ -298,9 +298,6 @@ class _ProfileEditState extends State { String name = ""; String server = ""; - bool? serverStatus; - bool serverChecking = false; - @override Widget build(BuildContext context) { return Scaffold( @@ -414,49 +411,6 @@ class _ProfileEditState extends State { return null; }, ), - Divider(), - SizedBox( - width: double.infinity, - child: ElevatedButton.icon( - label: Text(L10().connectionCheck), - icon: serverStatus == true - ? Icon(TablerIcons.circle_check, color: COLOR_SUCCESS) - : serverStatus == false - ? Icon(TablerIcons.circle_x, color: COLOR_DANGER) - : Icon(TablerIcons.question_mark, color: COLOR_WARNING), - onPressed: serverChecking - ? null - : () async { - if (serverChecking) { - return; - } - - if (!formKey.currentState!.validate()) { - return; - } - - if (mounted) { - setState(() { - serverStatus = null; - serverChecking = true; - }); - } - - formKey.currentState!.save(); - - InvenTreeAPI().checkServer(server: server).then(( - result, - ) { - if (mounted) { - setState(() { - serverStatus = result; - serverChecking = false; - }); - } - }); - }, - ), - ), ], ), padding: EdgeInsets.all(16), diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 231b03a..6bbf355 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -92,10 +92,9 @@ abstract class PaginatedSearchState // Skip null values if (value == null) { - f[k] = "null"; - } else { - f[k] = value.toString(); + continue; } + f[k] = value.toString(); } return f; @@ -207,7 +206,7 @@ abstract class PaginatedSearchState } Map filter = { - "type": "boolean filter", + "type": "boolean", "display_name": label, "label": label, "help_text": help_text, @@ -342,16 +341,7 @@ abstract class PaginatedSearchState Map f = await constructFilters(); if (f.isNotEmpty) { - for (String k in f.keys) { - // Remove any existing filter keys - dynamic value = f[k]; - - if (value == null || value == "null") { - params.remove(k); - } else { - params[k] = value.toString(); - } - } + params.addAll(f); } final page = await requestPage(_pageSize, pageKey, params); diff --git a/lib/widget/part/category_list.dart b/lib/widget/part/category_list.dart index fb74730..b90f2f6 100644 --- a/lib/widget/part/category_list.dart +++ b/lib/widget/part/category_list.dart @@ -54,7 +54,7 @@ class _PaginatedPartCategoryListState @override Map> get filterOptions => { "cascade": { - "default": true, + "default": false, "label": L10().includeSubcategories, "help_text": L10().includeSubcategoriesDetail, "tristate": false, diff --git a/lib/widget/stock/location_list.dart b/lib/widget/stock/location_list.dart index a808d27..d59c369 100644 --- a/lib/widget/stock/location_list.dart +++ b/lib/widget/stock/location_list.dart @@ -66,7 +66,6 @@ class _PaginatedStockLocationListState "label": L10().includeSublocations, "help_text": L10().includeSublocationsDetail, "tristate": false, - "default": true, }, }; diff --git a/lib/widget/stock/stock_detail.dart b/lib/widget/stock/stock_detail.dart index ed10212..282a962 100644 --- a/lib/widget/stock/stock_detail.dart +++ b/lib/widget/stock/stock_detail.dart @@ -225,7 +225,7 @@ class _StockItemDisplayState extends RefreshableState { // Request part information part = await InvenTreePart().get(widget.item.partId) as InvenTreePart?; - stockShowTests &= part?.isTestable ?? false; + stockShowTests &= part?.isTrackable ?? false; // Request default location int? defaultLocationId = part?.defaultLocation; diff --git a/pubspec.lock b/pubspec.lock index 9fd5914..ed42dec 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -637,14 +637,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" - jni: - dependency: transitive - description: - name: jni - sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 - url: "https://pub.dev" - source: hosted - version: "0.14.2" js: dependency: transitive description: @@ -745,10 +737,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce + sha256: "54005bdea7052d792d35b4fef0f84ec5ddc3a844b250ecd48dc192fb9b4ebc95" url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.0.1" node_preamble: dependency: transitive description: @@ -929,18 +921,18 @@ packages: dependency: transitive description: name: sentry - sha256: "605ad1f6f1ae5b72018cbe8fc20f490fa3bd53e58882e5579566776030d8c8c1" + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" url: "https://pub.dev" source: hosted - version: "9.14.0" + version: "8.14.2" sentry_flutter: dependency: "direct main" description: name: sentry_flutter - sha256: "7fd0fb80050c1f6a77ae185bda997a76d384326d6777cf5137a6c38952c4ac7d" + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" url: "https://pub.dev" source: hosted - version: "9.14.0" + version: "8.14.2" shared_preferences: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 19dfc9c..14f931f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: inventree description: InvenTree stock management -version: 0.22.7+116 +version: 0.22.1+110 environment: sdk: ^3.8.1 @@ -28,19 +28,19 @@ dependencies: flutter_markdown: ^0.6.19 # Rendering markdown flutter_overlay_loader: ^2.0.0 # Overlay screen support flutter_speed_dial: ^6.2.0 # Speed dial / FAB implementation - flutter_tabler_icons: ^1.43.0 # Tabler icons + flutter_tabler_icons: ^1.43.0 http: ^1.4.0 image_picker: ^1.1.2 # Select or take photos infinite_scroll_pagination: ^4.0.0 # Let the server do all the work! intl: ^0.20.2 - mobile_scanner: ^7.2.0 # Barcode scanning support + mobile_scanner: ^7.0.1 # Barcode scanning support one_context: ^4.0.0 # Dialogs without requiring context open_filex: ^4.7.0 # Open local files package_info_plus: ^8.1.1 # App information introspection path: ^1.9.0 path_provider: ^2.1.5 # Local file storage sembast: ^3.6.0 # NoSQL data storage - sentry_flutter: ^9.14.0 # Error reporting + sentry_flutter: 8.14.2 # Error reporting url_launcher: ^6.3.1 # Open link in system browser wakelock_plus: ^1.3.2 # Prevent device from sleeping