diff --git a/lib/api.dart b/lib/api.dart index aee69491..a1540908 100644 --- a/lib/api.dart +++ b/lib/api.dart @@ -27,13 +27,16 @@ import "package:inventree/user_profile.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/snacks.dart"; - /* * Class representing an API response from the server */ class APIResponse { - - APIResponse({this.url = "", this.method = "", this.statusCode = -1, this.error = "", this.data = const {}}); + APIResponse( + {this.url = "", + this.method = "", + this.statusCode = -1, + this.error = "", + this.data = const {}}); int statusCode = -1; @@ -88,7 +91,6 @@ class APIResponse { * Handles case where the response is paginated, or a complete set of results */ List resultsList() { - if (isList()) { return asList(); } else if (isMap()) { @@ -104,14 +106,12 @@ class APIResponse { } } - /* * Custom FileService for caching network images * Requires a custom badCertificateCallback, * so we can accept "dodgy" (e.g. self-signed) certificates */ class InvenTreeFileService extends FileService { - InvenTreeFileService({HttpClient? client, bool strictHttps = false}) { _client = client ?? HttpClient(); @@ -141,8 +141,10 @@ class InvenTreeFileService extends FileService { final HttpClientResponse httpResponse = await req.close(); final http.StreamedResponse _response = http.StreamedResponse( - httpResponse.timeout(Duration(seconds: 60)), httpResponse.statusCode, - contentLength: httpResponse.contentLength < 0 ? 0 : httpResponse.contentLength, + httpResponse.timeout(Duration(seconds: 60)), + httpResponse.statusCode, + contentLength: + httpResponse.contentLength < 0 ? 0 : httpResponse.contentLength, reasonPhrase: httpResponse.reasonPhrase, isRedirect: httpResponse.isRedirect, ); @@ -158,12 +160,10 @@ class InvenTreeFileService extends FileService { * initialised using a username:password combination. */ - /* * API class which manages all communication with the InvenTree server */ class InvenTreeAPI { - factory InvenTreeAPI() { return _api; } @@ -209,7 +209,6 @@ class InvenTreeAPI { } String _makeUrl(String url) { - // Strip leading slash if (url.startsWith("/")) { url = url.substring(1, url.length); @@ -257,15 +256,10 @@ class InvenTreeAPI { * Useful as a precursor check before performing operations. */ bool checkConnection() { - // Is the server connected? if (!isConnected()) { - - showSnackIcon( - L10().notConnected, - success: false, - icon: TablerIcons.server - ); + showSnackIcon(L10().notConnected, + success: false, icon: TablerIcons.server); return false; } @@ -388,7 +382,6 @@ class InvenTreeAPI { return !isConnected() && _connecting; } - /* * Perform the required login steps, in sequence. * Internal function, called by connectToServer() @@ -403,7 +396,6 @@ class InvenTreeAPI { * 5. Request information on available plugins */ Future _connectToServer() async { - if (!await _checkServer()) { return false; } @@ -413,7 +405,8 @@ class InvenTreeAPI { } if (!await _checkAuth()) { - showServerError(_URL_ME, L10().serverNotConnected, L10().serverAuthenticationError); + showServerError( + _URL_ME, L10().serverNotConnected, L10().serverAuthenticationError); // Invalidate the token if (profile != null) { @@ -436,21 +429,16 @@ class InvenTreeAPI { return true; } - /* * Check that the remote server is available. * Ping the api/ endpoint, which does not require user authentication */ Future _checkServer() async { - String address = profile?.server ?? ""; if (address.isEmpty) { - showSnackIcon( - L10().incompleteDetails, - icon: TablerIcons.exclamation_circle, - success: false - ); + showSnackIcon(L10().incompleteDetails, + icon: TablerIcons.exclamation_circle, success: false); return false; } @@ -459,7 +447,8 @@ class InvenTreeAPI { } // Cache the "strictHttps" setting, so we can use it later without async requirement - _strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool; + _strictHttps = await InvenTreeSettingsManager() + .getValue(INV_STRICT_HTTPS, false) as bool; debug("Connecting to ${apiUrl}"); @@ -467,7 +456,8 @@ class InvenTreeAPI { if (!response.successful()) { debug("Server returned invalid response: ${response.statusCode}"); - showStatusCodeError(apiUrl, response.statusCode, details: response.data.toString()); + showStatusCodeError(apiUrl, response.statusCode, + details: response.data.toString()); return false; } @@ -486,7 +476,6 @@ class InvenTreeAPI { } if (apiVersion < _minApiVersion) { - String message = L10().serverApiVersion + ": ${apiVersion}"; message += "\n"; @@ -509,7 +498,6 @@ class InvenTreeAPI { return true; } - /* * Check that the user is authenticated * Fetch the user information @@ -525,7 +513,8 @@ class InvenTreeAPI { userInfo = response.asMap(); return true; } else { - debug("Auth request failed: Server returned status ${response.statusCode}"); + debug( + "Auth request failed: Server returned status ${response.statusCode}"); if (response.data != null) { debug("Server response: ${response.data.toString()}"); } @@ -538,8 +527,8 @@ class InvenTreeAPI { * Fetch a token from the server, * with a temporary authentication header */ - Future fetchToken(UserProfile userProfile, String username, String password) async { - + Future fetchToken( + UserProfile userProfile, String username, String password) async { debug("Fetching user token from ${userProfile.server}"); profile = userProfile; @@ -574,14 +563,13 @@ class InvenTreeAPI { } // Construct auth header from username and password - String authHeader = "Basic " + base64Encode(utf8.encode("${username}:${password}")); + String authHeader = + "Basic " + base64Encode(utf8.encode("${username}:${password}")); // Perform request to get a token - final response = await get( - _URL_TOKEN, - params: { "name": platform_name}, - headers: { HttpHeaders.authorizationHeader: authHeader} - ); + final response = await get(_URL_TOKEN, + params: {"name": platform_name}, + headers: {HttpHeaders.authorizationHeader: authHeader}); // Invalid response if (!response.successful()) { @@ -643,22 +631,17 @@ class InvenTreeAPI { _connectionStatusChanged(); } - /* Public facing connection function. */ Future connectToServer(UserProfile prf) async { - // Ensure server is first disconnected disconnectFromServer(); profile = prf; if (profile == null) { - showSnackIcon( - L10().profileSelect, - success: false, - icon: TablerIcons.exclamation_circle - ); + showSnackIcon(L10().profileSelect, + success: false, icon: TablerIcons.exclamation_circle); return false; } @@ -681,11 +664,9 @@ class InvenTreeAPI { if (_notification_timer == null) { debug("starting notification timer"); - _notification_timer = Timer.periodic( - Duration(seconds: 5), - (timer) { - _refreshNotifications(); - }); + _notification_timer = Timer.periodic(Duration(seconds: 5), (timer) { + _refreshNotifications(); + }); } } @@ -700,7 +681,6 @@ class InvenTreeAPI { * Request the user roles (permissions) from the InvenTree server */ Future _fetchRoles() async { - roles.clear(); debug("API: Requesting user role data"); @@ -714,7 +694,6 @@ class InvenTreeAPI { var data = response.asMap(); if (!data.containsKey("roles")) { - roles = {}; permissions = {}; @@ -739,7 +718,6 @@ class InvenTreeAPI { // Request plugin information from the server Future _fetchPlugins() async { - _plugins.clear(); debug("API: getPluginInformation()"); @@ -764,7 +742,6 @@ class InvenTreeAPI { * e.g. "sales_order", "change" */ bool checkRole(String role, String permission) { - if (!_connected) { return false; } @@ -793,15 +770,11 @@ class InvenTreeAPI { // Ignore TypeError } else { // Unknown error - report it! - sentryReportError( - "api.checkRole", - error, stackTrace, - context: { - "role": role, - "permission": permission, - "error": error.toString(), - } - ); + sentryReportError("api.checkRole", error, stackTrace, context: { + "role": role, + "permission": permission, + "error": error.toString(), + }); } // Unable to determine permission - assume true? @@ -841,15 +814,11 @@ class InvenTreeAPI { // Ignore TypeError } else { // Unknown error - report it! - sentryReportError( - "api.checkPermission", - error, stackTrace, - context: { - "model": model, - "permission": permission, - "error": error.toString(), - } - ); + sentryReportError("api.checkPermission", error, stackTrace, context: { + "model": model, + "permission": permission, + "error": error.toString(), + }); } // Unable to determine permission - assume true? @@ -857,10 +826,9 @@ class InvenTreeAPI { } } - // Perform a PATCH request - Future patch(String url, {Map body = const {}, int? expectedStatusCode}) async { - + Future patch(String url, + {Map body = const {}, int? expectedStatusCode}) async { Map _body = body; HttpClientRequest? request = await apiRequest(url, "PATCH"); @@ -868,24 +836,17 @@ class InvenTreeAPI { if (request == null) { // Return an "invalid" APIResponse return APIResponse( - url: url, - method: "PATCH", - error: "HttpClientRequest is null" - ); + url: url, method: "PATCH", error: "HttpClientRequest is null"); } - return completeRequest( - request, - data: json.encode(_body), - statusCode: expectedStatusCode - ); + return completeRequest(request, + data: json.encode(_body), statusCode: expectedStatusCode); } /* * Download a file from the given URL */ Future downloadFile(String url, {bool openOnDownload = true}) async { - if (url.isEmpty) { // No URL provided for download return; @@ -912,19 +873,20 @@ class InvenTreeAPI { HttpClientRequest? _request; - final bool strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool; + final bool strictHttps = await InvenTreeSettingsManager() + .getValue(INV_STRICT_HTTPS, false) as bool; var client = createClient(url, strictHttps: strictHttps); // Attempt to open a connection to the server try { - _request = await client.openUrl("GET", _uri).timeout(Duration(seconds: 10)); + _request = + await client.openUrl("GET", _uri).timeout(Duration(seconds: 10)); // Set headers defaultHeaders().forEach((key, value) { _request?.headers.set(key, value); }); - } on SocketException catch (error) { debug("SocketException at ${url}: ${error.toString()}"); showServerError(url, L10().connectionRefused, error.toString()); @@ -943,7 +905,8 @@ class InvenTreeAPI { showServerError(url, L10().serverError, error.toString()); sentryReportError( "api.downloadFile : client.openUrl", - error, stackTrace, + error, + stackTrace, ); return; } @@ -974,7 +937,8 @@ class InvenTreeAPI { showServerError(url, L10().downloadError, error.toString()); sentryReportError( "api.downloadFile : client.closeRequest", - error, stackTrace, + error, + stackTrace, ); } } @@ -983,7 +947,9 @@ class InvenTreeAPI { * Upload a file to the given URL */ Future uploadFile(String url, File f, - {String name = "attachment", String method="POST", Map? fields}) async { + {String name = "attachment", + String method = "POST", + Map? fields}) async { var _url = makeApiUrl(url); var request = http.MultipartRequest(method, Uri.parse(_url)); @@ -992,7 +958,6 @@ class InvenTreeAPI { if (fields != null) { fields.forEach((String key, dynamic value) { - if (value == null) { request.fields[key] = ""; } else { @@ -1023,48 +988,35 @@ class InvenTreeAPI { // Report a server-side error if (response.statusCode == 500) { - sentryReportMessage( - "Server error in uploadFile()", - context: { - "url": url, - "method": request.method, - "name": name, - "statusCode": response.statusCode.toString(), - "requestHeaders": request.headers.toString(), - "responseHeaders": httpResponse.headers.toString(), - } - ); + sentryReportMessage("Server error in uploadFile()", context: { + "url": url, + "method": request.method, + "name": name, + "statusCode": response.statusCode.toString(), + "requestHeaders": request.headers.toString(), + "responseHeaders": httpResponse.headers.toString(), + }); } } on SocketException catch (error) { showServerError(url, L10().connectionRefused, error.toString()); response.error = "SocketException"; response.errorDetail = error.toString(); } on FormatException { - showServerError( - url, - L10().formatException, - L10().formatExceptionJson + ":\n${jsondata}" - ); - - sentryReportMessage( - "Error decoding JSON response from server", - context: { - "method": "uploadFile", - "url": url, - "statusCode": response.statusCode.toString(), - "data": jsondata, - } - ); + showServerError(url, L10().formatException, + L10().formatExceptionJson + ":\n${jsondata}"); + sentryReportMessage("Error decoding JSON response from server", context: { + "method": "uploadFile", + "url": url, + "statusCode": response.statusCode.toString(), + "data": jsondata, + }); } on TimeoutException { showTimeoutError(url); response.error = "TimeoutException"; } catch (error, stackTrace) { showServerError(url, L10().serverError, error.toString()); - sentryReportError( - "api.uploadFile", - error, stackTrace - ); + sentryReportError("api.uploadFile", error, stackTrace); response.error = "UnknownError"; response.errorDetail = error.toString(); } @@ -1079,15 +1031,11 @@ class InvenTreeAPI { * so that (hopefully) the field messages are correctly translated */ Future options(String url) async { - HttpClientRequest? request = await apiRequest(url, "OPTIONS"); if (request == null) { // Return an "invalid" APIResponse - return APIResponse( - url: url, - method: "OPTIONS" - ); + return APIResponse(url: url, method: "OPTIONS"); } return completeRequest(request); @@ -1097,51 +1045,40 @@ class InvenTreeAPI { * Perform a HTTP POST request * Returns a json object (or null if unsuccessful) */ - Future post(String url, {Map body = const {}, int? expectedStatusCode=201}) async { - + Future post(String url, + {Map body = const {}, + int? expectedStatusCode = 201}) async { HttpClientRequest? request = await apiRequest(url, "POST"); if (request == null) { // Return an "invalid" APIResponse - return APIResponse( - url: url, - method: "POST" - ); + return APIResponse(url: url, method: "POST"); } - return completeRequest( - request, - data: json.encode(body), - statusCode: expectedStatusCode - ); + return completeRequest(request, + data: json.encode(body), statusCode: expectedStatusCode); } /* * 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; + } - if (request == null) { - return false; - } - - final response = await completeRequest( - request, - data: json.encode(body), - statusCode: 200 - ); - - return response.isValid() && response.statusCode == 200; + final response = await completeRequest(request, + data: json.encode(body), statusCode: 200); + return response.isValid() && response.statusCode == 200; } /* * Perform a request to unlink a custom barcode from a particular item */ Future unlinkBarcode(Map body) async { - HttpClientRequest? request = await apiRequest("/barcode/unlink/", "POST"); if (request == null) { @@ -1149,21 +1086,19 @@ class InvenTreeAPI { } final response = await completeRequest( - request, - data: json.encode(body), - statusCode: 200, + request, + data: json.encode(body), + statusCode: 200, ); return response.isValid() && response.statusCode == 200; } - HttpClient createClient(String url, {bool strictHttps = false}) { - var client = HttpClient(); - client.badCertificateCallback = (X509Certificate cert, String host, int port) { - + client.badCertificateCallback = + (X509Certificate cert, String host, int port) { if (strictHttps) { showServerError( url, @@ -1191,22 +1126,15 @@ class InvenTreeAPI { * @param params is the request parameters */ Future apiRequest( - String url, - String method, - { - Map urlParams = const {}, - Map headers = const {}, - } - ) async { - + String url, + String method, { + Map urlParams = const {}, + Map headers = const {}, + }) async { var _url = makeApiUrl(url); if (_url.isEmpty) { - showServerError( - url, - L10().invalidHost, - L10().invalidHostDetails - ); + showServerError(url, L10().invalidHost, L10().invalidHostDetails); return null; } @@ -1227,23 +1155,21 @@ class InvenTreeAPI { Uri? _uri = Uri.tryParse(_url); if (_uri == null || _uri.host.isEmpty) { - showServerError( - _url, - L10().invalidHost, - L10().invalidHostDetails - ); + showServerError(_url, L10().invalidHost, L10().invalidHostDetails); return null; } HttpClientRequest? _request; - final bool strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool; + final bool strictHttps = await InvenTreeSettingsManager() + .getValue(INV_STRICT_HTTPS, false) as bool; var client = createClient(url, strictHttps: strictHttps); // Attempt to open a connection to the server try { - _request = await client.openUrl(method, _uri).timeout(Duration(seconds: 10)); + _request = + await client.openUrl(method, _uri).timeout(Duration(seconds: 10)); // Default headers defaultHeaders().forEach((key, value) { @@ -1281,42 +1207,37 @@ class InvenTreeAPI { } catch (error, stackTrace) { debug("Server error at ${url}: ${error.toString()}"); showServerError(url, L10().serverError, error.toString()); - sentryReportError( - "api.apiRequest : openUrl", - error, stackTrace, - context: { - "url": url, - "method": method, - } - ); + sentryReportError("api.apiRequest : openUrl", error, stackTrace, + context: { + "url": url, + "method": method, + }); return null; } } - /* * Complete an API request, and return an APIResponse object */ - Future completeRequest(HttpClientRequest request, {String? data, int? statusCode, bool ignoreResponse = false}) async { - + Future completeRequest(HttpClientRequest request, + {String? data, int? statusCode, bool ignoreResponse = false}) async { if (data != null && data.isNotEmpty) { - var encoded_data = utf8.encode(data); - request.headers.set(HttpHeaders.contentLengthHeader, encoded_data.length.toString()); + request.headers + .set(HttpHeaders.contentLengthHeader, encoded_data.length.toString()); request.add(encoded_data); } - APIResponse response = APIResponse( - method: request.method, - url: request.uri.toString() - ); + APIResponse response = + APIResponse(method: request.method, url: request.uri.toString()); String url = request.uri.toString(); try { - HttpClientResponse? _response = await request.close().timeout(Duration(seconds: 10)); + HttpClientResponse? _response = + await request.close().timeout(Duration(seconds: 10)); response.statusCode = _response.statusCode; @@ -1326,31 +1247,29 @@ class InvenTreeAPI { // Some server errors are not ones for us to worry about! switch (_response.statusCode) { - case 502: // Bad gateway - case 503: // Service unavailable - case 504: // Gateway timeout + case 502: // Bad gateway + case 503: // Service unavailable + case 504: // Gateway timeout break; - default: // Any other error code - sentryReportMessage( - "Server error", - context: { - "url": request.uri.toString(), - "method": request.method, - "statusCode": _response.statusCode.toString(), - "requestHeaders": request.headers.toString(), - "responseHeaders": _response.headers.toString(), - "responseData": response.data.toString(), - } - ); + default: // Any other error code + sentryReportMessage("Server error", context: { + "url": request.uri.toString(), + "method": request.method, + "statusCode": _response.statusCode.toString(), + "requestHeaders": request.headers.toString(), + "responseHeaders": _response.headers.toString(), + "responseData": response.data.toString(), + }); break; } } else { - - response.data = ignoreResponse ? {} : await responseToJson(url, _response) ?? {}; + response.data = + ignoreResponse ? {} : await responseToJson(url, _response) ?? {}; // First check that the returned status code is what we expected if (statusCode != null && statusCode != _response.statusCode) { - showStatusCodeError(url, _response.statusCode, details: response.data.toString()); + showStatusCodeError(url, _response.statusCode, + details: response.data.toString()); } } } on HttpException catch (error) { @@ -1376,14 +1295,12 @@ class InvenTreeAPI { } return response; - } /* * Convert a HttpClientResponse response object to JSON */ dynamic responseToJson(String url, HttpClientResponse response) async { - String body = await response.transform(utf8.decoder).join(); try { @@ -1391,7 +1308,6 @@ class InvenTreeAPI { return data ?? {}; } on FormatException { - switch (response.statusCode) { case 400: case 401: @@ -1405,36 +1321,32 @@ class InvenTreeAPI { // Ignore for server errors break; default: - sentryReportMessage( - "Error decoding JSON response from server", + sentryReportMessage("Error decoding JSON response from server", context: { "headers": response.headers.toString(), "statusCode": response.statusCode.toString(), "data": body.toString(), "endpoint": url, - } - ); + }); break; } showServerError( - url, - L10().formatException, - L10().formatExceptionJson + ":\n${body}" - ); + url, L10().formatException, L10().formatExceptionJson + ":\n${body}"); // Return an empty map return {}; } - } /* * Perform a HTTP GET request * Returns a json object (or null if did not complete) */ - Future get(String url, {Map params = const {}, Map headers = const {}, int? expectedStatusCode=200}) async { - + Future get(String url, + {Map params = const {}, + Map headers = const {}, + int? expectedStatusCode = 200}) async { HttpClientRequest? request = await apiRequest( url, "GET", @@ -1442,7 +1354,6 @@ class InvenTreeAPI { headers: headers, ); - if (request == null) { // Return an "invalid" APIResponse return APIResponse( @@ -1459,7 +1370,6 @@ class InvenTreeAPI { * Perform a HTTP DELETE request */ Future delete(String url) async { - HttpClientRequest? request = await apiRequest( url, "DELETE", @@ -1482,15 +1392,12 @@ class InvenTreeAPI { // Find the current locale code for the running app String get currentLocale { - if (hasContext()) { // Try to get app context BuildContext? context = OneContext().context; if (context != null) { - Locale? locale = InvenTreeApp - .of(context) - ?.locale; + Locale? locale = InvenTreeApp.of(context)?.locale; if (locale != null) { return locale.languageCode; //.toString(); @@ -1530,8 +1437,8 @@ class InvenTreeAPI { static String get staticThumb => "/static/img/blank_image.thumbnail.png"; - CachedNetworkImage? getThumbnail(String imageUrl, {double size = 40, bool hideIfNull = false}) { - + CachedNetworkImage? getThumbnail(String imageUrl, + {double size = 40, bool hideIfNull = false}) { if (hideIfNull) { if (imageUrl.isEmpty) { return null; @@ -1539,11 +1446,7 @@ class InvenTreeAPI { } try { - return getImage( - imageUrl, - width: size, - height: size - ); + return getImage(imageUrl, width: size, height: size); } catch (error, stackTrace) { sentryReportError("_getThumbnail", error, stackTrace); return null; @@ -1554,7 +1457,8 @@ class InvenTreeAPI { * Load image from the InvenTree server, * or from local cache (if it has been cached!) */ - CachedNetworkImage getImage(String imageUrl, {double? height, double? width}) { + CachedNetworkImage getImage(String imageUrl, + {double? height, double? width}) { if (imageUrl.isEmpty) { imageUrl = staticImage; } @@ -1563,19 +1467,18 @@ class InvenTreeAPI { const key = "inventree_network_image"; - CacheManager manager = CacheManager( - Config( - key, - fileService: InvenTreeFileService( - strictHttps: _strictHttps, - ), - ) - ); + CacheManager manager = CacheManager(Config( + key, + fileService: InvenTreeFileService( + strictHttps: _strictHttps, + ), + )); return CachedNetworkImage( imageUrl: url, placeholder: (context, url) => CircularProgressIndicator(), - errorWidget: (context, url, error) => Icon(TablerIcons.circle_x, color: COLOR_DANGER), + errorWidget: (context, url, error) => + Icon(TablerIcons.circle_x, color: COLOR_DANGER), httpHeaders: defaultHeaders(), height: height, width: width, @@ -1588,7 +1491,6 @@ class InvenTreeAPI { Map _userSettings = {}; Future getGlobalSetting(String key) async { - InvenTreeGlobalSetting? setting = _globalSettings[key]; if ((setting != null) && setting.reloadedWithin(Duration(minutes: 5))) { @@ -1607,7 +1509,8 @@ class InvenTreeAPI { } // Return a boolean global setting value - Future getGlobalBooleanSetting(String key, { bool backup = false }) async { + Future getGlobalBooleanSetting(String key, + {bool backup = false}) async { String value = await getGlobalSetting(key); if (value.isEmpty) { @@ -1618,7 +1521,6 @@ class InvenTreeAPI { } Future getUserSetting(String key) async { - InvenTreeUserSetting? setting = _userSettings[key]; if ((setting != null) && setting.reloadedWithin(Duration(minutes: 5))) { @@ -1645,8 +1547,8 @@ class InvenTreeAPI { /* * Send a request to the server to locate / identify either a StockItem or StockLocation */ - Future locateItemOrLocation(BuildContext context, {int? item, int? location}) async { - + Future locateItemOrLocation(BuildContext context, + {int? item, int? location}) async { var plugins = getPlugins(mixin: "locate"); if (plugins.isEmpty) { @@ -1679,16 +1581,11 @@ class InvenTreeAPI { } }; - await launchApiForm( - context, - L10().locateLocation, - "", - fields, + await launchApiForm(context, L10().locateLocation, "", fields, icon: TablerIcons.location_search, onSuccess: (Map data) async { - plugin_name = (data["plugin"] ?? "") as String; - } - ); + plugin_name = (data["plugin"] ?? "") as String; + }); } Map body = { @@ -1730,10 +1627,13 @@ class InvenTreeAPI { } // Accessors methods for various status code classes - InvenTreeStatusCode get StockHistoryStatus => _get_status_class("stock/track/status/"); + InvenTreeStatusCode get StockHistoryStatus => + _get_status_class("stock/track/status/"); InvenTreeStatusCode get StockStatus => _get_status_class("stock/status/"); - InvenTreeStatusCode get PurchaseOrderStatus => _get_status_class("order/po/status/"); - InvenTreeStatusCode get SalesOrderStatus => _get_status_class("order/so/status/"); + InvenTreeStatusCode get PurchaseOrderStatus => + _get_status_class("order/po/status/"); + InvenTreeStatusCode get SalesOrderStatus => + _get_status_class("order/so/status/"); void clearStatusCodeData() { StockHistoryStatus.data.clear(); @@ -1766,5 +1666,3 @@ class InvenTreeAPI { }); } } - - diff --git a/lib/api_form.dart b/lib/api_form.dart index c1cabbd4..dc802a84 100644 --- a/lib/api_form.dart +++ b/lib/api_form.dart @@ -1,4 +1,3 @@ - import "dart:io"; import "package:intl/intl.dart"; @@ -27,13 +26,11 @@ import "package:inventree/widget/fields.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; - /* * Class that represents a single "form field", * defined by the InvenTree API */ class APIFormField { - // Constructor APIFormField(this.name, this.data); @@ -53,7 +50,6 @@ class APIFormField { // Return the "lookup path" for this field, within the server data String get lookupPath { - // Simple top-level case if (parent.isEmpty && !nested) { return name; @@ -133,19 +129,16 @@ class APIFormField { // Construct a set of "filters" for this field (e.g. related field) Map get filters { - Map _filters = {}; // Start with the field "definition" (provided by the server) if (definition.containsKey("filters")) { - try { var fDef = definition["filters"] as Map; fDef.forEach((String key, dynamic value) { _filters[key] = value.toString(); }); - } catch (error) { // pass } @@ -153,7 +146,6 @@ class APIFormField { // Next, look at any "instance_filters" provided by the server if (definition.containsKey("instance_filters")) { - try { var fIns = definition["instance_filters"] as Map; @@ -163,7 +155,6 @@ class APIFormField { } catch (error) { // pass } - } // Finally, augment or override with any filters provided by the calling function @@ -180,14 +171,12 @@ class APIFormField { } return _filters; - } bool hasErrors() => errorMessages().isNotEmpty; // Extract error messages from the server response void extractErrorMessages(APIResponse response) { - dynamic errors; if (isSimple) { @@ -213,7 +202,6 @@ class APIFormField { // Return the error message associated with this field List errorMessages() { - dynamic errors = data["errors"] ?? []; // Handle the case where a single error message is returned @@ -246,7 +234,6 @@ class APIFormField { List get choices => (getParameter("choices") ?? []) as List; Future loadInitialData() async { - // Only for "related fields" if (type != "related field") { return; @@ -277,7 +264,6 @@ class APIFormField { // Construct a widget for this input Widget constructField(BuildContext context) { - switch (type) { case "string": case "url": @@ -300,19 +286,15 @@ class APIFormField { return _constructBarcodeField(context); default: return ListTile( - title: Text( - "Unsupported field type: '${type}' for field '${name}'", - style: TextStyle( - color: COLOR_DANGER, - fontStyle: FontStyle.italic), - ) - ); + title: Text( + "Unsupported field type: '${type}' for field '${name}'", + style: TextStyle(color: COLOR_DANGER, fontStyle: FontStyle.italic), + )); } } // Field for capturing a barcode Widget _constructBarcodeField(BuildContext context) { - TextEditingController controller = TextEditingController(); String barcode = (value ?? "").toString(); @@ -324,114 +306,104 @@ class APIFormField { controller.text = barcode; return InputDecorator( - decoration: InputDecoration( - labelText: required ? label + "*" : label, - labelStyle: _labelStyle(), - helperText: helpText, - helperStyle: _helperStyle(), - hintText: placeholderText, - ), - child: ListTile( - title: TextField( - readOnly: true, - controller: controller, + decoration: InputDecoration( + labelText: required ? label + "*" : label, + labelStyle: _labelStyle(), + helperText: helpText, + helperStyle: _helperStyle(), + hintText: placeholderText, ), - trailing: IconButton( - icon: Icon(TablerIcons.qrcode), - onPressed: () async { - var handler = UniqueBarcodeHandler((String hash) { - controller.text = hash; - data["value"] = hash; + child: ListTile( + title: TextField( + readOnly: true, + controller: controller, + ), + trailing: IconButton( + icon: Icon(TablerIcons.qrcode), + onPressed: () async { + var handler = UniqueBarcodeHandler((String hash) { + controller.text = hash; + data["value"] = hash; - barcodeSuccess(L10().barcodeAssigned); - }); - - scanBarcode(context, handler: handler); - }, - ), - ) - ); + barcodeSuccess(L10().barcodeAssigned); + }); + scanBarcode(context, handler: handler); + }, + ), + )); } // Field for displaying and selecting dates Widget _constructDateField() { - - DateTime? currentDate = DateTime.tryParse((value ?? "")as String); + DateTime? currentDate = DateTime.tryParse((value ?? "") as String); return InputDecorator( - decoration: InputDecoration( - labelText: label, - labelStyle: _labelStyle(), - helperStyle: _helperStyle(), - helperText: helpText, - ), - child: DateTimeField( - format: DateFormat("yyyy-MM-dd"), - initialValue: currentDate, - onChanged: (DateTime? time) { - // Save the time string - if (time == null) { - data["value"] = null; - } else { - data["value"] = time.toString().split(" ").first; - } - }, - onShowPicker: (context, value) async { - final time = await showDatePicker( - context: context, - initialDate: currentDate ?? DateTime.now(), - firstDate: DateTime(1900), - lastDate: DateTime(2100), - ); - - return time; - }, - ) - ); + decoration: InputDecoration( + labelText: label, + labelStyle: _labelStyle(), + helperStyle: _helperStyle(), + helperText: helpText, + ), + child: DateTimeField( + format: DateFormat("yyyy-MM-dd"), + initialValue: currentDate, + onChanged: (DateTime? time) { + // Save the time string + if (time == null) { + data["value"] = null; + } else { + data["value"] = time.toString().split(" ").first; + } + }, + onShowPicker: (context, value) async { + final time = await showDatePicker( + context: context, + initialDate: currentDate ?? DateTime.now(), + firstDate: DateTime(1900), + lastDate: DateTime(2100), + ); + return time; + }, + )); } - // Field for selecting and uploading files Widget _constructFileField() { - TextEditingController controller = TextEditingController(); - controller.text = (attachedfile?.path ?? L10().attachmentSelect).split("/").last; + controller.text = + (attachedfile?.path ?? L10().attachmentSelect).split("/").last; return InputDecorator( - decoration: InputDecoration( - labelText: label, - labelStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), - ), - child: ListTile( - title: TextField( - readOnly: true, - controller: controller, + decoration: InputDecoration( + labelText: label, + labelStyle: TextStyle(fontWeight: FontWeight.bold, fontSize: 18), ), - trailing: IconButton( - icon: Icon(TablerIcons.circle_plus), - onPressed: () async { - FilePickerDialog.pickFile( - message: L10().attachmentSelect, - onPicked: (file) { - // Display the filename - controller.text = file.path.split("/").last; + child: ListTile( + title: TextField( + readOnly: true, + controller: controller, + ), + trailing: IconButton( + icon: Icon(TablerIcons.circle_plus), + onPressed: () async { + FilePickerDialog.pickFile( + message: L10().attachmentSelect, + onPicked: (file) { + // Display the filename + controller.text = file.path.split("/").last; - // Save the file - attachedfile = file; - } - ); - }, - ) - ) - ); + // Save the file + attachedfile = file; + }); + }, + ))); } // Field for selecting from multiple choice options Widget _constructChoiceField() { - dynamic initial; // Check if the current value is within the allowed values @@ -443,36 +415,32 @@ class APIFormField { } return DropdownSearch( - popupProps: PopupProps.bottomSheet( - showSelectedItems: false, - searchFieldProps: TextFieldProps( - autofocus: true - ) - ), - selectedItem: initial, - items: choices, - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - labelText: label, - hintText: helpText, - )), - onChanged: null, - clearButtonProps: ClearButtonProps(isVisible: !required), - itemAsString: (dynamic item) { - return (item["display_name"] ?? "") as String; - }, - onSaved: (item) { - if (item == null) { - data["value"] = null; - } else { - data["value"] = item["value"]; - } - }); + popupProps: PopupProps.bottomSheet( + showSelectedItems: false, + searchFieldProps: TextFieldProps(autofocus: true)), + selectedItem: initial, + items: choices, + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + labelText: label, + hintText: helpText, + )), + onChanged: null, + clearButtonProps: ClearButtonProps(isVisible: !required), + itemAsString: (dynamic item) { + return (item["display_name"] ?? "") as String; + }, + onSaved: (item) { + if (item == null) { + data["value"] = null; + } else { + data["value"] = item["value"]; + } + }); } // Construct a floating point numerical input field Widget _constructFloatField() { - // Initial value: try to cast to a valid number String initial = ""; @@ -491,7 +459,8 @@ class APIFormField { hintText: placeholderText, ), initialValue: initial, - keyboardType: TextInputType.numberWithOptions(signed: true, decimal: true), + keyboardType: + TextInputType.numberWithOptions(signed: true, decimal: true), validator: (value) { value = value?.trim() ?? ""; @@ -512,107 +481,101 @@ class APIFormField { data["value"] = val; }, ); - } // Construct an input for a related field Widget _constructRelatedField() { return DropdownSearch( - popupProps: PopupProps.bottomSheet( - showSelectedItems: true, - isFilterOnline: true, - showSearchBox: true, - itemBuilder: (context, item, isSelected) { - return _renderRelatedField(name, item, isSelected, true); + popupProps: PopupProps.bottomSheet( + showSelectedItems: true, + isFilterOnline: true, + showSearchBox: true, + itemBuilder: (context, item, isSelected) { + return _renderRelatedField(name, item, isSelected, true); + }, + emptyBuilder: (context, item) { + return _renderEmptyResult(); + }, + searchFieldProps: TextFieldProps(autofocus: true)), + selectedItem: initial_data, + asyncItems: (String filter) async { + Map _filters = { + ..._relatedFieldFilters(), + ...filters, + }; + + _filters["search"] = filter; + _filters["offset"] = "0"; + _filters["limit"] = "25"; + + final APIResponse response = + await InvenTreeAPI().get(api_url, params: _filters); + + if (response.isValid()) { + return response.resultsList(); + } else { + return []; + } }, - emptyBuilder: (context, item) { - return _renderEmptyResult(); + clearButtonProps: ClearButtonProps(isVisible: !required), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + labelText: label, + hintText: helpText, + )), + onChanged: null, + itemAsString: (dynamic item) { + Map data = item as Map; + + switch (model) { + case InvenTreePart.MODEL_TYPE: + return InvenTreePart.fromJson(data).fullname; + case InvenTreeCompany.MODEL_TYPE: + return InvenTreeCompany.fromJson(data).name; + case InvenTreePurchaseOrder.MODEL_TYPE: + return InvenTreePurchaseOrder.fromJson(data).reference; + case InvenTreeSalesOrder.MODEL_TYPE: + return InvenTreeSalesOrder.fromJson(data).reference; + case InvenTreePartCategory.MODEL_TYPE: + return InvenTreePartCategory.fromJson(data).pathstring; + case InvenTreeStockLocation.MODEL_TYPE: + return InvenTreeStockLocation.fromJson(data).pathstring; + default: + return "itemAsString not implemented for '${model}'"; + } }, - searchFieldProps: TextFieldProps( - autofocus: true - ) - ), - selectedItem: initial_data, - asyncItems: (String filter) async { - Map _filters = { - ..._relatedFieldFilters(), - ...filters, - }; + dropdownBuilder: (context, item) { + return _renderRelatedField(name, item, true, false); + }, + onSaved: (item) { + if (item != null) { + data["value"] = item["pk"]; + } else { + data["value"] = null; + } + }, + compareFn: (dynamic item, dynamic selectedItem) { + // Comparison is based on the PK value - _filters["search"] = filter; - _filters["offset"] = "0"; - _filters["limit"] = "25"; + if (item == null || selectedItem == null) { + return false; + } - final APIResponse response = await InvenTreeAPI().get(api_url, params: _filters); + bool result = false; - if (response.isValid()) { - return response.resultsList(); - } else { - return []; - } - }, - clearButtonProps: ClearButtonProps( - isVisible: !required - ), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - labelText: label, - hintText: helpText, - )), - onChanged: null, - itemAsString: (dynamic item) { - Map data = item as Map; + try { + result = item["pk"].toString() == selectedItem["pk"].toString(); + } catch (error) { + // Catch any conversion errors + result = false; + } - switch (model) { - case InvenTreePart.MODEL_TYPE: - return InvenTreePart.fromJson(data).fullname; - case InvenTreeCompany.MODEL_TYPE: - return InvenTreeCompany.fromJson(data).name; - case InvenTreePurchaseOrder.MODEL_TYPE: - return InvenTreePurchaseOrder.fromJson(data).reference; - case InvenTreeSalesOrder.MODEL_TYPE: - return InvenTreeSalesOrder.fromJson(data).reference; - case InvenTreePartCategory.MODEL_TYPE: - return InvenTreePartCategory.fromJson(data).pathstring; - case InvenTreeStockLocation.MODEL_TYPE: - return InvenTreeStockLocation.fromJson(data).pathstring; - default: - return "itemAsString not implemented for '${model}'"; - } - }, - dropdownBuilder: (context, item) { - return _renderRelatedField(name, item, true, false); - }, - onSaved: (item) { - if (item != null) { - data["value"] = item["pk"]; - } else { - data["value"] = null; - } - }, - compareFn: (dynamic item, dynamic selectedItem) { - // Comparison is based on the PK value - - if (item == null || selectedItem == null) { - return false; - } - - bool result = false; - - try { - result = item["pk"].toString() == selectedItem["pk"].toString(); - } catch (error) { - // Catch any conversion errors - result = false; - } - - return result; - }); + return result; + }); } // Construct a set of custom filters for the dropdown search Map _relatedFieldFilters() { - switch (model) { case InvenTreeSupplierPart.MODEL_TYPE: return InvenTreeSupplierPart().defaultListFilters(); @@ -626,8 +589,8 @@ class APIFormField { } // Render a "related field" based on the "model" type - Widget _renderRelatedField(String fieldName, dynamic item, bool selected, bool extended) { - + Widget _renderRelatedField( + String fieldName, dynamic item, bool selected, bool extended) { // Convert to JSON Map data = {}; @@ -640,16 +603,13 @@ class APIFormField { } catch (error, stackTrace) { data = {}; - sentryReportError( - "_renderRelatedField", error, stackTrace, - context: { - "method": "_renderRelateField", - "field_name": fieldName, - "item": item.toString(), - "selected": selected.toString(), - "extended": extended.toString(), - } - ); + sentryReportError("_renderRelatedField", error, stackTrace, context: { + "method": "_renderRelateField", + "field_name": fieldName, + "item": item.toString(), + "selected": selected.toString(), + "extended": extended.toString(), + }); } switch (model) { @@ -657,45 +617,58 @@ class APIFormField { var part = InvenTreePart.fromJson(data); return ListTile( - title: Text( - part.fullname, - style: TextStyle(fontWeight: selected && extended ? FontWeight.bold : FontWeight.normal) - ), - subtitle: extended ? Text( - part.description, - style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), - ) : null, - leading: extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null, + title: Text(part.fullname, + style: TextStyle( + fontWeight: selected && extended + ? FontWeight.bold + : FontWeight.normal)), + subtitle: extended + ? Text( + part.description, + style: TextStyle( + fontWeight: + selected ? FontWeight.bold : FontWeight.normal), + ) + : null, + leading: + extended ? InvenTreeAPI().getThumbnail(part.thumbnail) : null, ); case InvenTreePartTestTemplate.MODEL_TYPE: - var template = InvenTreePartTestTemplate.fromJson(data); + var template = InvenTreePartTestTemplate.fromJson(data); - return ListTile( - title: Text(template.testName), - subtitle: Text(template.description), - ); + return ListTile( + title: Text(template.testName), + subtitle: Text(template.description), + ); case InvenTreeSupplierPart.MODEL_TYPE: var part = InvenTreeSupplierPart.fromJson(data); return ListTile( title: Text(part.SKU), subtitle: Text(part.partName), - leading: extended ? InvenTreeAPI().getThumbnail(part.partImage) : null, - trailing: extended && part.supplierImage.isNotEmpty ? InvenTreeAPI().getThumbnail(part.supplierImage) : null, + leading: + extended ? InvenTreeAPI().getThumbnail(part.partImage) : null, + trailing: extended && part.supplierImage.isNotEmpty + ? InvenTreeAPI().getThumbnail(part.supplierImage) + : null, ); case InvenTreePartCategory.MODEL_TYPE: - var cat = InvenTreePartCategory.fromJson(data); return ListTile( - title: Text( - cat.pathstring, - style: TextStyle(fontWeight: selected && extended ? FontWeight.bold : FontWeight.normal) - ), - subtitle: extended ? Text( - cat.description, - style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), - ) : null, + title: Text(cat.pathstring, + style: TextStyle( + fontWeight: selected && extended + ? FontWeight.bold + : FontWeight.normal)), + subtitle: extended + ? Text( + cat.description, + style: TextStyle( + fontWeight: + selected ? FontWeight.bold : FontWeight.normal), + ) + : null, ); case InvenTreeStockItem.MODEL_TYPE: var item = InvenTreeStockItem.fromJson(data); @@ -711,14 +684,19 @@ class APIFormField { var loc = InvenTreeStockLocation.fromJson(data); return ListTile( - title: Text( - loc.pathstring, - style: TextStyle(fontWeight: selected && extended ? FontWeight.bold : FontWeight.normal) - ), - subtitle: extended ? Text( - loc.description, - style: TextStyle(fontWeight: selected ? FontWeight.bold : FontWeight.normal), - ) : null, + title: Text(loc.pathstring, + style: TextStyle( + fontWeight: selected && extended + ? FontWeight.bold + : FontWeight.normal)), + subtitle: extended + ? Text( + loc.description, + style: TextStyle( + fontWeight: + selected ? FontWeight.bold : FontWeight.normal), + ) + : null, ); case InvenTreeSalesOrderShipment.MODEL_TYPE: var shipment = InvenTreeSalesOrderShipment.fromJson(data); @@ -747,24 +725,18 @@ class APIFormField { return ListTile( title: Text(company.name), subtitle: extended ? Text(company.description) : null, - leading: InvenTreeAPI().getThumbnail(company.thumbnail) - ); + leading: InvenTreeAPI().getThumbnail(company.thumbnail)); case InvenTreeProjectCode.MODEL_TYPE: var project_code = InvenTreeProjectCode.fromJson(data); return ListTile( title: Text(project_code.code), subtitle: Text(project_code.description), - leading: Icon(TablerIcons.list) - ); + leading: Icon(TablerIcons.list)); default: return ListTile( - title: Text( - "Unsupported model", - style: TextStyle( - fontWeight: FontWeight.bold, - color: COLOR_DANGER - ) - ), + title: Text("Unsupported model", + style: + TextStyle(fontWeight: FontWeight.bold, color: COLOR_DANGER)), subtitle: Text("Model '${model}' rendering not supported"), ); } @@ -782,10 +754,8 @@ class APIFormField { ); } - // Construct a string input element Widget _constructString() { - if (readOnly) { return ListTile( title: Text(label), @@ -821,7 +791,6 @@ class APIFormField { // Construct a boolean input element Widget _constructBoolean() { - bool? initial_value; if (value is bool || value == null) { @@ -860,15 +829,12 @@ class APIFormField { color: hasErrors() ? COLOR_DANGER : null, ); } - } - /* * Extract field options from a returned OPTIONS request */ Map extractFields(APIResponse response) { - if (!response.isValid()) { return {}; } @@ -896,8 +862,8 @@ Map extractFields(APIResponse response) { * The map "tree" is traversed based on the provided lookup string, which can use dotted notation. * This allows complex paths to be used to lookup field information. */ -Map extractFieldDefinition(Map data, String lookup) { - +Map extractFieldDefinition( + Map data, String lookup) { List path = lookup.split("."); // Shadow copy the data for path traversal @@ -905,7 +871,6 @@ Map extractFieldDefinition(Map data, String lo // Iterate through all but the last element of the path for (int ii = 0; ii < (path.length - 1); ii++) { - String el = path[ii]; if (!_data.containsKey(el)) { @@ -922,13 +887,11 @@ Map extractFieldDefinition(Map data, String lo // Report the error sentryReportError( - "apiForm.extractFieldDefinition : path traversal", - error, stackTrace, - context: { - "path": path.toString(), - "el": el, - } - ); + "apiForm.extractFieldDefinition : path traversal", error, stackTrace, + context: { + "path": path.toString(), + "el": el, + }); return {}; } } @@ -938,7 +901,6 @@ Map extractFieldDefinition(Map data, String lo if (!_data.containsKey(el)) { return {}; } else { - try { Map definition = _data[el] as Map; @@ -949,20 +911,16 @@ Map extractFieldDefinition(Map data, String lo // Report the error sentryReportError( - "apiForm.extractFieldDefinition : as map", - error, stacktrace, - context: { - "el": el.toString(), - } - ); + "apiForm.extractFieldDefinition : as map", error, stacktrace, + context: { + "el": el.toString(), + }); return {}; } - } } - /* * Launch an API-driven form, * which uses the OPTIONS metadata (at the provided URL) @@ -977,23 +935,19 @@ Map extractFieldDefinition(Map data, String lo Future launchApiForm( BuildContext context, String title, String url, Map fields, - { - String fileField = "", - Map modelData = const {}, - String method = "PATCH", - Function(Map)? onSuccess, - bool Function(Map)? validate, - Function? onCancel, - IconData icon = TablerIcons.device_floppy - }) async { - + {String fileField = "", + Map modelData = const {}, + String method = "PATCH", + Function(Map)? onSuccess, + bool Function(Map)? validate, + Function? onCancel, + IconData icon = TablerIcons.device_floppy}) async { showLoadingOverlay(); // List of fields defined by the server Map serverFields = {}; if (url.isNotEmpty) { - var options = await InvenTreeAPI().options(url); // Invalid response from server @@ -1022,7 +976,6 @@ Future launchApiForm( APIFormField field; for (String fieldName in fields.keys) { - dynamic data = fields[fieldName]; Map fieldData = {}; @@ -1065,36 +1018,32 @@ Future launchApiForm( // Now, launch a new widget! Navigator.push( - context, - MaterialPageRoute(builder: (context) => APIFormWidget( - title, - url, - formFields, - method, - onSuccess: onSuccess, - validate: validate, - fileField: fileField, - icon: icon, - )) - ); + context, + MaterialPageRoute( + builder: (context) => APIFormWidget( + title, + url, + formFields, + method, + onSuccess: onSuccess, + validate: validate, + fileField: fileField, + icon: icon, + ))); } - class APIFormWidget extends StatefulWidget { - const APIFormWidget( - this.title, - this.url, - this.fields, - this.method, - { - Key? key, - this.onSuccess, - this.validate, - this.fileField = "", - this.icon = TablerIcons.device_floppy, - } - ) : super(key: key); + this.title, + this.url, + this.fields, + this.method, { + Key? key, + this.onSuccess, + this.validate, + this.fileField = "", + this.icon = TablerIcons.device_floppy, + }) : super(key: key); //! Form title to display final String title; @@ -1118,12 +1067,9 @@ class APIFormWidget extends StatefulWidget { @override _APIFormWidgetState createState() => _APIFormWidgetState(); - } - class _APIFormWidgetState extends State { - _APIFormWidgetState() : super(); final _formKey = GlobalKey(); @@ -1133,34 +1079,26 @@ class _APIFormWidgetState extends State { bool spacerRequired = false; List _buildForm() { - List widgets = []; // Display non-field errors first if (nonFieldErrors.isNotEmpty) { for (String error in nonFieldErrors) { - widgets.add( - ListTile( - title: Text( - error, - style: TextStyle( - color: COLOR_DANGER, - ), + widgets.add(ListTile( + title: Text( + error, + style: TextStyle( + color: COLOR_DANGER, ), - leading: Icon( - TablerIcons.exclamation_circle, - color: COLOR_DANGER - ), - ) - ); + ), + leading: Icon(TablerIcons.exclamation_circle, color: COLOR_DANGER), + )); } widgets.add(Divider(height: 5)); - } for (var field in widget.fields) { - if (field.hidden) { continue; } @@ -1181,18 +1119,15 @@ class _APIFormWidgetState extends State { if (field.hasErrors()) { for (String error in field.errorMessages()) { - widgets.add( - ListTile( + widgets.add(ListTile( title: Text( - error, - style: TextStyle( - color: COLOR_DANGER, - fontStyle: FontStyle.italic, - fontSize: 16, - ), - ) - ) - ); + error, + style: TextStyle( + color: COLOR_DANGER, + fontStyle: FontStyle.italic, + fontSize: 16, + ), + ))); } } @@ -1213,20 +1148,16 @@ class _APIFormWidgetState extends State { } Future _submit(Map data) async { - // If a file upload is required, we have to handle the submission differently if (widget.fileField.isNotEmpty) { - // Pop the "file" field data.remove(widget.fileField); for (var field in widget.fields) { if (field.name == widget.fileField) { - File? file = field.attachedfile; if (file != null) { - // A valid file has been supplied final response = await InvenTreeAPI().uploadFile( widget.url, @@ -1242,24 +1173,16 @@ class _APIFormWidgetState extends State { } if (widget.method == "POST") { - showLoadingOverlay(); - final response = await InvenTreeAPI().post( - widget.url, - body: data, - expectedStatusCode: null - ); + final response = await InvenTreeAPI() + .post(widget.url, body: data, expectedStatusCode: null); hideLoadingOverlay(); return response; - } else { showLoadingOverlay(); - final response = await InvenTreeAPI().patch( - widget.url, - body: data, - expectedStatusCode: null - ); + final response = await InvenTreeAPI() + .patch(widget.url, body: data, expectedStatusCode: null); hideLoadingOverlay(); return response; @@ -1267,7 +1190,6 @@ class _APIFormWidgetState extends State { } void extractNonFieldErrors(APIResponse response) { - List errors = []; Map data = response.asMap(); @@ -1304,7 +1226,6 @@ class _APIFormWidgetState extends State { var errors = response.asMap(); for (String fieldName in errors.keys) { - bool match = false; switch (fieldName) { @@ -1316,7 +1237,6 @@ class _APIFormWidgetState extends State { continue; default: for (var field in widget.fields) { - // Hidden fields can't display errors, so we won't match if (field.hidden) { continue; @@ -1327,7 +1247,6 @@ class _APIFormWidgetState extends State { match = true; break; } else if (field.parent == fieldName) { - var error = errors[fieldName]; if (error is List) { @@ -1349,15 +1268,13 @@ class _APIFormWidgetState extends State { if (!match) { // Match for an unknown / unsupported field - sentryReportMessage( - "API form returned error for unsupported field", - context: { - "url": response.url, - "status_code": response.statusCode.toString(), - "field": fieldName, - "error_message": response.data.toString(), - } - ); + sentryReportMessage("API form returned error for unsupported field", + context: { + "url": response.url, + "status_code": response.statusCode.toString(), + "field": fieldName, + "error_message": response.data.toString(), + }); } } } @@ -1366,14 +1283,12 @@ class _APIFormWidgetState extends State { * Submit the form data to the server, and handle the results */ Future _save(BuildContext context) async { - // Package up the form data Map data = {}; // Iterate through and find "simple" top-level fields for (var field in widget.fields) { - if (field.readOnly) { continue; } @@ -1384,7 +1299,6 @@ class _APIFormWidgetState extends State { } else { // Not so simple... (WHY DID I MAKE THE API SO COMPLEX?) if (field.parent.isNotEmpty) { - // TODO: This is a dirty hack, there *must* be a cleaner way?! dynamic parent = data[field.parent] ?? {}; @@ -1406,7 +1320,7 @@ class _APIFormWidgetState extends State { } } } - + final bool isValid = widget.validate?.call(data) ?? true; if (!isValid) { @@ -1446,7 +1360,6 @@ class _APIFormWidgetState extends State { Navigator.pop(context); if (successFunc != null) { - // Ensure the response is a valid JSON structure Map json = {}; @@ -1475,10 +1388,7 @@ class _APIFormWidgetState extends State { checkInvalidErrors(response); break; case 401: - showSnackIcon( - "401: " + L10().response401, - success: false - ); + showSnackIcon("401: " + L10().response401, success: false); break; case 403: showSnackIcon( @@ -1515,43 +1425,36 @@ class _APIFormWidgetState extends State { setState(() { // Refresh the form }); - } @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - backgroundColor: COLOR_APP_BAR, - actions: [ - IconButton( - icon: Icon(widget.icon), - onPressed: () { - - if (_formKey.currentState!.validate()) { - _formKey.currentState!.save(); - - _save(context); - } - }, - ) - ] - ), - body: Form( - key: _formKey, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: _buildForm(), - ), - padding: EdgeInsets.all(16), - ) - ) - ); + appBar: AppBar( + title: Text(widget.title), + backgroundColor: COLOR_APP_BAR, + actions: [ + IconButton( + icon: Icon(widget.icon), + onPressed: () { + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); + _save(context); + } + }, + ) + ]), + body: Form( + key: _formKey, + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildForm(), + ), + padding: EdgeInsets.all(16), + ))); } } diff --git a/lib/app_colors.dart b/lib/app_colors.dart index 5ed9e20a..aeda838c 100644 --- a/lib/app_colors.dart +++ b/lib/app_colors.dart @@ -4,7 +4,6 @@ import "package:inventree/helpers.dart"; import "package:one_context/one_context.dart"; bool isDarkMode() { - if (!hasContext()) { return false; } diff --git a/lib/barcode/barcode.dart b/lib/barcode/barcode.dart index 0de089e7..ba53cbde 100644 --- a/lib/barcode/barcode.dart +++ b/lib/barcode/barcode.dart @@ -10,7 +10,6 @@ import "package:inventree/widget/company/manufacturer_part_detail.dart"; import "package:inventree/widget/order/sales_order_detail.dart"; import "package:one_context/one_context.dart"; - import "package:inventree/api.dart"; import "package:inventree/l10.dart"; @@ -35,10 +34,8 @@ import "package:inventree/widget/stock/stock_detail.dart"; import "package:inventree/widget/company/company_detail.dart"; import "package:inventree/widget/company/supplier_part_detail.dart"; - // Signal a barcode scan success to the user Future barcodeSuccess(String msg) async { - barcodeSuccessTone(); showSnackIcon(msg, success: true); } @@ -46,26 +43,18 @@ Future barcodeSuccess(String msg) async { // Signal a barcode scan failure to the user Future barcodeFailure(String msg, dynamic extra) async { barcodeFailureTone(); - showSnackIcon( - msg, - success: false, - onAction: () { - if (hasContext()) { - OneContext().showDialog( - builder: (BuildContext context) => - SimpleDialog( - title: Text(L10().barcodeError), - children: [ - ListTile( - title: Text(L10().responseData), - subtitle: Text(extra.toString()) - ) - ] - ) - ); - } + showSnackIcon(msg, success: false, onAction: () { + if (hasContext()) { + OneContext().showDialog( + builder: (BuildContext context) => SimpleDialog( + title: Text(L10().barcodeError), + children: [ + ListTile( + title: Text(L10().responseData), + subtitle: Text(extra.toString())) + ])); } - ); + }); } /* @@ -75,15 +64,16 @@ Future barcodeFailure(String msg, dynamic extra) async { * - Returns a Future which resolves when the scanner is dismissed * - The provided BarcodeHandler instance is used to handle the scanned barcode */ -Future scanBarcode(BuildContext context, {BarcodeHandler? handler}) async { - +Future scanBarcode(BuildContext context, + {BarcodeHandler? handler}) async { // Default to generic scan handler handler ??= BarcodeScanHandler(); - + InvenTreeBarcodeController controller = CameraBarcodeController(handler); // Select barcode controller based on user preference - final int barcodeControllerType = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int; + final int barcodeControllerType = await InvenTreeSettingsManager() + .getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int; switch (barcodeControllerType) { case BARCODE_CONTROLLER_WEDGE: @@ -95,15 +85,12 @@ Future scanBarcode(BuildContext context, {BarcodeHandler? handler}) asy break; } - return Navigator.of(context).push( - PageRouteBuilder( - pageBuilder: (context, _, __) => controller, - opaque: false, - ) - ); + return Navigator.of(context).push(PageRouteBuilder( + pageBuilder: (context, _, __) => controller, + opaque: false, + )); } - /* * Class for general barcode scanning. * Scan *any* barcode without context, and then redirect app to correct view. @@ -117,19 +104,17 @@ Future scanBarcode(BuildContext context, {BarcodeHandler? handler}) asy * - PurchaseOrder */ class BarcodeScanHandler extends BarcodeHandler { - @override String getOverlayText(BuildContext context) => L10().barcodeScanGeneral; @override Future onBarcodeUnknown(Map data) async { - barcodeFailureTone(); showSnackIcon( - L10().barcodeNoMatch, - icon: TablerIcons.exclamation_circle, - success: false, + L10().barcodeNoMatch, + icon: TablerIcons.exclamation_circle, + success: false, ); } @@ -137,12 +122,12 @@ class BarcodeScanHandler extends BarcodeHandler { * Response when a "Part" instance is scanned */ Future handlePart(int pk) async { - var part = await InvenTreePart().get(pk); if (part is InvenTreePart) { OneContext().pop(); - OneContext().push(MaterialPageRoute(builder: (context) => PartDetailWidget(part))); + OneContext().push( + MaterialPageRoute(builder: (context) => PartDetailWidget(part))); } } @@ -150,13 +135,12 @@ class BarcodeScanHandler extends BarcodeHandler { * Response when a "StockItem" instance is scanned */ Future handleStockItem(int pk) async { - var item = await InvenTreeStockItem().get(pk); if (item is InvenTreeStockItem) { OneContext().pop(); - OneContext().push(MaterialPageRoute( - builder: (context) => StockDetailWidget(item))); + OneContext().push( + MaterialPageRoute(builder: (context) => StockDetailWidget(item))); } } @@ -164,13 +148,12 @@ class BarcodeScanHandler extends BarcodeHandler { * Response when a "StockLocation" instance is scanned */ Future handleStockLocation(int pk) async { - var loc = await InvenTreeStockLocation().get(pk); if (loc is InvenTreeStockLocation) { OneContext().pop(); - OneContext().navigator.push(MaterialPageRoute( - builder: (context) => LocationDisplayWidget(loc))); + OneContext().navigator.push( + MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc))); } } @@ -178,7 +161,6 @@ class BarcodeScanHandler extends BarcodeHandler { * Response when a "SupplierPart" instance is scanned */ Future handleSupplierPart(int pk) async { - var supplierPart = await InvenTreeSupplierPart().get(pk); if (supplierPart is InvenTreeSupplierPart) { @@ -197,7 +179,8 @@ class BarcodeScanHandler extends BarcodeHandler { if (manufacturerPart is InvenTreeManufacturerPart) { OneContext().pop(); OneContext().push(MaterialPageRoute( - builder: (context) => ManufacturerPartDetailWidget(manufacturerPart))); + builder: (context) => + ManufacturerPartDetailWidget(manufacturerPart))); } } @@ -220,7 +203,7 @@ class BarcodeScanHandler extends BarcodeHandler { if (order is InvenTreePurchaseOrder) { OneContext().pop(); OneContext().push(MaterialPageRoute( - builder: (context) => PurchaseOrderDetailWidget(order))); + builder: (context) => PurchaseOrderDetailWidget(order))); } } @@ -231,7 +214,7 @@ class BarcodeScanHandler extends BarcodeHandler { if (order is InvenTreeSalesOrder) { OneContext().pop(); OneContext().push(MaterialPageRoute( - builder: (context) => SalesOrderDetailWidget(order))); + builder: (context) => SalesOrderDetailWidget(order))); } } @@ -251,7 +234,6 @@ class BarcodeScanHandler extends BarcodeHandler { InvenTreeManufacturerPart.MODEL_TYPE, ]; - if (InvenTreeAPI().supportsOrderBarcodes) { validModels.add(InvenTreePurchaseOrder.MODEL_TYPE); validModels.add(InvenTreeSalesOrder.MODEL_TYPE); @@ -275,7 +257,6 @@ class BarcodeScanHandler extends BarcodeHandler { // A valid result has been found if (pk > 0 && model.isNotEmpty) { - barcodeSuccessTone(); switch (model) { @@ -312,36 +293,27 @@ class BarcodeScanHandler extends BarcodeHandler { // If we get here, we have not found a valid barcode result! barcodeFailureTone(); - showSnackIcon( - L10().barcodeUnknown, - success: false, - onAction: () { - - if (hasContext()) { - OneContext().showDialog( - builder: (BuildContext context) => - SimpleDialog( - title: Text(L10().unknownResponse), - children: [ - ListTile( - title: Text(L10().responseData), - subtitle: Text(data.toString()), - ) - ], + showSnackIcon(L10().barcodeUnknown, success: false, onAction: () { + if (hasContext()) { + OneContext().showDialog( + builder: (BuildContext context) => SimpleDialog( + title: Text(L10().unknownResponse), + children: [ + ListTile( + title: Text(L10().responseData), + subtitle: Text(data.toString()), ) - ); - } - } - ); + ], + )); + } + }); } } - /* * Barcode handler for finding a "unique" barcode (one that does not match an item in the database) */ class UniqueBarcodeHandler extends BarcodeHandler { - UniqueBarcodeHandler(this.callback, {this.overlayText = ""}); // Callback function when a "unique" barcode hash is found @@ -379,7 +351,6 @@ class UniqueBarcodeHandler extends BarcodeHandler { success: false, ); } else { - barcodeSuccessTone(); // Close the barcode scanner @@ -396,49 +367,43 @@ class UniqueBarcodeHandler extends BarcodeHandler { Future onBarcodeUnknown(Map data) async { await onBarcodeMatched(data); } - } - -SpeedDialChild customBarcodeAction(BuildContext context, RefreshableState state, String barcode, String model, int pk) { - +SpeedDialChild customBarcodeAction(BuildContext context, RefreshableState state, + String barcode, String model, int pk) { if (barcode.isEmpty) { return SpeedDialChild( - label: L10().barcodeAssign, - child: Icon(Icons.barcode_reader), - onTap: () { - var handler = UniqueBarcodeHandler((String barcode) { - InvenTreeAPI().linkBarcode({ - model: pk.toString(), - "barcode": barcode, - }).then((bool result) { + label: L10().barcodeAssign, + child: Icon(Icons.barcode_reader), + onTap: () { + var handler = UniqueBarcodeHandler((String barcode) { + InvenTreeAPI().linkBarcode({ + model: pk.toString(), + "barcode": barcode, + }).then((bool result) { + showSnackIcon( + result ? L10().barcodeAssigned : L10().barcodeNotAssigned, + success: result); + + state.refresh(context); + }); + }); + scanBarcode(context, handler: handler); + }); + } else { + return SpeedDialChild( + child: Icon(Icons.barcode_reader), + label: L10().barcodeUnassign, + onTap: () { + InvenTreeAPI() + .unlinkBarcode({model: pk.toString()}).then((bool result) { showSnackIcon( - result ? L10().barcodeAssigned : L10().barcodeNotAssigned, - success: result + result ? L10().requestSuccessful : L10().requestFailed, + success: result, ); state.refresh(context); }); }); - scanBarcode(context, handler: handler); - } - ); - } else { - return SpeedDialChild( - child: Icon(Icons.barcode_reader), - label: L10().barcodeUnassign, - onTap: () { - InvenTreeAPI().unlinkBarcode({ - model: pk.toString() - }).then((bool result) { - showSnackIcon( - result ? L10().requestSuccessful : L10().requestFailed, - success: result, - ); - - state.refresh(context); - }); - } - ); } } diff --git a/lib/barcode/camera_controller.dart b/lib/barcode/camera_controller.dart index 7e44de59..6282a06e 100644 --- a/lib/barcode/camera_controller.dart +++ b/lib/barcode/camera_controller.dart @@ -42,9 +42,8 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { String scanned_code = ""; - final MobileScannerController controller = MobileScannerController( - autoZoom: true - ); + final MobileScannerController controller = + MobileScannerController(autoZoom: true); @override void initState() { @@ -80,7 +79,6 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Future pauseScan() async { - if (mounted) { setState(() { scanning_paused = true; @@ -90,7 +88,6 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Future resumeScan() async { - controller.start(); if (mounted) { @@ -114,8 +111,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { setState(() { multiple_barcodes = false; }); - } - else if (result.barcodes.length > 1) { + } else if (result.barcodes.length > 1) { setState(() { multiple_barcodes = true; }); @@ -175,18 +171,12 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { void onControllerCreated(CameraController? controller, Exception? error) { if (error != null) { sentryReportError( - "CameraBarcodeController.onControllerCreated", - error, - null - ); + "CameraBarcodeController.onControllerCreated", error, null); } if (controller == null) { - showSnackIcon( - L10().cameraCreationError, - icon: TablerIcons.camera_x, - success: false - ); + showSnackIcon(L10().cameraCreationError, + icon: TablerIcons.camera_x, success: false); if (OneContext.hasContext) { Navigator.pop(OneContext().context!); @@ -195,7 +185,6 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { } Widget BarcodeOverlay(BuildContext context) { - final Size screenSize = MediaQuery.of(context).size; final double width = screenSize.width; final double height = screenSize.height; @@ -213,29 +202,25 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { overlayColor = COLOR_WARNING; } - return Stack( - children: [ - Center( + return Stack(children: [ + Center( child: Container( - width: D, - height: D, - decoration: BoxDecoration( - border: Border.all( - color: overlayColor, - width: 4, - ), - ), - ) - ) - ] - ); + width: D, + height: D, + decoration: BoxDecoration( + border: Border.all( + color: overlayColor, + width: 4, + ), + ), + )) + ]); } /* * Build the barcode reader widget */ Widget BarcodeReader(BuildContext context) { - final Size screenSize = MediaQuery.of(context).size; final double width = screenSize.width; final double height = screenSize.height; @@ -248,10 +233,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { return BarcodeOverlay(context); }, scanWindow: Rect.fromCenter( - center: Offset(width / 2, height / 2), - width: D, - height: D - ), + center: Offset(width / 2, height / 2), width: D, height: D), onDetect: (result) { onScanSuccess(result); }, @@ -260,31 +242,21 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { Widget topCenterOverlay() { return SafeArea( - child: Align( - alignment: Alignment.topCenter, - child: Padding( - padding: EdgeInsets.only( - left: 10, - right: 10, - top: 75, - bottom: 10 - ), - child: Text( - widget.handler.getOverlayText(context), - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold - ) - ) - ) - ) - ); + child: Align( + alignment: Alignment.topCenter, + child: Padding( + padding: + EdgeInsets.only(left: 10, right: 10, top: 75, bottom: 10), + child: Text(widget.handler.getOverlayText(context), + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold))))); } Widget bottomCenterOverlay() { - - String info_text = scanning_paused ? L10().barcodeScanPaused : L10().barcodeScanPause; + String info_text = + scanning_paused ? L10().barcodeScanPaused : L10().barcodeScanPause; String text = scanned_code.isNotEmpty ? scanned_code : info_text; @@ -293,51 +265,39 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { } return SafeArea( - child: Align( - alignment: Alignment.bottomCenter, - child: Padding( - padding: EdgeInsets.only( - left: 10, - right: 10, - top: 10, - bottom: 75 - ), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.white, - fontSize: 16, - fontWeight: FontWeight.bold - ) - ), - ) - ) - ); + child: Align( + alignment: Alignment.bottomCenter, + child: Padding( + padding: + EdgeInsets.only(left: 10, right: 10, top: 10, bottom: 75), + child: Text(text, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 16, + fontWeight: FontWeight.bold)), + ))); } Widget? buildActions(BuildContext context) { - List actions = [ SpeedDialChild( - child: Icon(flash_status ? TablerIcons.bulb_off : TablerIcons.bulb), - label: L10().toggleTorch, - onTap: () async { - controller.toggleTorch(); - if (mounted) { - setState(() { - flash_status = !flash_status; - }); - } - } - ), + child: Icon(flash_status ? TablerIcons.bulb_off : TablerIcons.bulb), + label: L10().toggleTorch, + onTap: () async { + controller.toggleTorch(); + if (mounted) { + setState(() { + flash_status = !flash_status; + }); + } + }), SpeedDialChild( - child: Icon(TablerIcons.camera), - label: L10().switchCamera, - onTap: () async { - controller.switchCamera(); - } - ) + child: Icon(TablerIcons.camera), + label: L10().switchCamera, + onTap: () async { + controller.switchCamera(); + }) ]; return SpeedDial( @@ -348,7 +308,6 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Widget build(BuildContext context) { - return Scaffold( appBar: AppBar( backgroundColor: COLOR_APP_BAR, @@ -368,9 +327,7 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { children: [ Column( children: [ - Expanded( - child: BarcodeReader(context) - ), + Expanded(child: BarcodeReader(context)), ], ), topCenterOverlay(), @@ -380,5 +337,4 @@ class _CameraBarcodeControllerState extends InvenTreeBarcodeControllerState { ), ); } - } diff --git a/lib/barcode/controller.dart b/lib/barcode/controller.dart index 3a8fa2c5..f14b61d7 100644 --- a/lib/barcode/controller.dart +++ b/lib/barcode/controller.dart @@ -11,7 +11,6 @@ import "package:inventree/widget/progress.dart"; * which is used to process the scanned barcode. */ class InvenTreeBarcodeController extends StatefulWidget { - const InvenTreeBarcodeController(this.handler, {Key? key}) : super(key: key); final BarcodeHandler handler; @@ -20,16 +19,16 @@ class InvenTreeBarcodeController extends StatefulWidget { State createState() => InvenTreeBarcodeControllerState(); } - /* * Base state widget for the barcode controller. * This defines the basic interface for the barcode controller. */ -class InvenTreeBarcodeControllerState extends State { - +class InvenTreeBarcodeControllerState + extends State { InvenTreeBarcodeControllerState() : super(); - final GlobalKey barcodeControllerKey = GlobalKey(debugLabel: "barcodeController"); + final GlobalKey barcodeControllerKey = + GlobalKey(debugLabel: "barcodeController"); // Internal state flag to test if we are currently processing a barcode bool processingBarcode = false; @@ -40,7 +39,6 @@ class InvenTreeBarcodeControllerState extends State * Barcode data should be passed as a string */ Future handleBarcodeData(String? data) async { - // Check that the data is valid, and this view is still mounted if (!mounted || data == null || data.isEmpty) { return; @@ -66,7 +64,8 @@ class InvenTreeBarcodeControllerState extends State return; } - int delay = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500) as int; + int delay = await InvenTreeSettingsManager() + .getValue(INV_BARCODE_SCAN_DELAY, 500) as int; Future.delayed(Duration(milliseconds: delay), () { hideLoadingOverlay(); @@ -99,5 +98,4 @@ class InvenTreeBarcodeControllerState extends State Widget build(BuildContext context) { return Container(); } - -} \ No newline at end of file +} diff --git a/lib/barcode/handler.dart b/lib/barcode/handler.dart index 48831f9a..63deafca 100644 --- a/lib/barcode/handler.dart +++ b/lib/barcode/handler.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -13,7 +12,6 @@ import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/snacks.dart"; - /* Generic class which "handles" a barcode, by communicating with the InvenTree server, * and handling match / unknown / error cases. * @@ -21,7 +19,6 @@ import "package:inventree/widget/snacks.dart"; * based on the response returned from the InvenTree server */ class BarcodeHandler { - BarcodeHandler(); // Return the text to display on the barcode overlay @@ -60,21 +57,16 @@ class BarcodeHandler { Future processBarcode(String barcode, {String url = "barcode/", Map extra_data = const {}}) async { - debug("Scanned barcode data: '${barcode}'"); barcode = barcode.trim(); // Empty barcode is invalid if (barcode.isEmpty) { - barcodeFailureTone(); - showSnackIcon( - L10().barcodeError, - icon: TablerIcons.exclamation_circle, - success: false - ); + showSnackIcon(L10().barcodeError, + icon: TablerIcons.exclamation_circle, success: false); return; } @@ -123,8 +115,7 @@ class BarcodeHandler { "error": response.error, "errorDetail": response.errorDetail, "className": "${this}", - } - ); + }); } else if (data.containsKey("success")) { await onBarcodeMatched(data); } else if ((response.statusCode >= 400) || data.containsKey("error")) { diff --git a/lib/barcode/purchase_order.dart b/lib/barcode/purchase_order.dart index 8b2f984b..5dfcdce5 100644 --- a/lib/barcode/purchase_order.dart +++ b/lib/barcode/purchase_order.dart @@ -20,7 +20,6 @@ import "package:inventree/widget/snacks.dart"; * - If location or quantity information wasn't provided, show a form to fill it in */ class POReceiveBarcodeHandler extends BarcodeHandler { - POReceiveBarcodeHandler({this.purchaseOrder, this.location, this.lineItem}); InvenTreePurchaseOrder? purchaseOrder; @@ -33,9 +32,9 @@ class POReceiveBarcodeHandler extends BarcodeHandler { @override Future processBarcode(String barcode, {String url = "barcode/po-receive/", - Map extra_data = const {}}) async { - - final bool confirm = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true); + Map extra_data = const {}}) async { + final bool confirm = + await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true); final po_extra_data = { "purchase_order": purchaseOrder?.pk, @@ -50,7 +49,6 @@ class POReceiveBarcodeHandler extends BarcodeHandler { @override Future onBarcodeMatched(Map data) async { - if (data.containsKey("lineitem") || data.containsKey("success")) { barcodeSuccess(L10().receivedItem); return; @@ -66,7 +64,8 @@ class POReceiveBarcodeHandler extends BarcodeHandler { } final lineItemData = data["lineitem"] as Map; - if (!lineItemData.containsKey("pk") || !lineItemData.containsKey("purchase_order")) { + if (!lineItemData.containsKey("pk") || + !lineItemData.containsKey("purchase_order")) { barcodeFailureTone(); showSnackIcon(L10().missingData, success: false); } @@ -79,7 +78,8 @@ class POReceiveBarcodeHandler extends BarcodeHandler { return; } - InvenTreePOLineItem? lineItem = await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?; + InvenTreePOLineItem? lineItem = + await InvenTreePOLineItem().get(lineItemId) as InvenTreePOLineItem?; if (lineItem == null) { barcodeFailureTone(); @@ -89,7 +89,8 @@ class POReceiveBarcodeHandler extends BarcodeHandler { // Next, extract the "optional" fields // Extract information from the returned server response - double? quantity = double.tryParse((lineItemData["quantity"] ?? "0").toString()); + double? quantity = + double.tryParse((lineItemData["quantity"] ?? "0").toString()); int? destination = lineItemData["location"] as int?; String? barcode = data["barcode_data"] as String?; @@ -98,33 +99,26 @@ class POReceiveBarcodeHandler extends BarcodeHandler { OneContext().pop(); } - await lineItem.receive( - OneContext().context!, - destination: destination, - quantity: quantity, - barcode: barcode, - onSuccess: () { - showSnackIcon(L10().receivedItem, success: true); - } - ); + await lineItem.receive(OneContext().context!, + destination: destination, + quantity: quantity, + barcode: barcode, onSuccess: () { + showSnackIcon(L10().receivedItem, success: true); + }); } @override Future onBarcodeUnknown(Map data) async { barcodeFailureTone(); - showSnackIcon( - data["error"] as String? ?? L10().barcodeError, - success: false - ); + showSnackIcon(data["error"] as String? ?? L10().barcodeError, + success: false); } } - /* * Barcode handler to add a line item to a purchase order */ class POAllocateBarcodeHandler extends BarcodeHandler { - POAllocateBarcodeHandler({this.purchaseOrder}); InvenTreePurchaseOrder? purchaseOrder; @@ -133,11 +127,9 @@ class POAllocateBarcodeHandler extends BarcodeHandler { String getOverlayText(BuildContext context) => L10().scanSupplierPart; @override - Future processBarcode(String barcode, { - String url = "barcode/po-allocate/", - Map extra_data = const {}} - ) { - + Future processBarcode(String barcode, + {String url = "barcode/po-allocate/", + Map extra_data = const {}}) { final po_extra_data = { "purchase_order": purchaseOrder?.pk, ...extra_data, @@ -189,10 +181,9 @@ class POAllocateBarcodeHandler extends BarcodeHandler { @override Future onBarcodeUnhandled(Map data) async { - print("onBarcodeUnhandled:"); print(data.toString()); super.onBarcodeUnhandled(data); } -} \ No newline at end of file +} diff --git a/lib/barcode/sales_order.dart b/lib/barcode/sales_order.dart index e0e79ad0..871e0ed9 100644 --- a/lib/barcode/sales_order.dart +++ b/lib/barcode/sales_order.dart @@ -14,13 +14,11 @@ import "package:inventree/barcode/tones.dart"; import "package:inventree/widget/snacks.dart"; - /* * Barcode handler class for scanning a new part into a SalesOrder */ class SOAddItemBarcodeHandler extends BarcodeHandler { - SOAddItemBarcodeHandler({this.salesOrder}); InvenTreeSalesOrder? salesOrder; @@ -30,7 +28,6 @@ class SOAddItemBarcodeHandler extends BarcodeHandler { @override Future onBarcodeMatched(Map data) async { - // Extract the part ID from the returned data int part_id = -1; @@ -46,7 +43,6 @@ class SOAddItemBarcodeHandler extends BarcodeHandler { var part = await InvenTreePart().get(part_id); if (part is InvenTreePart) { - if (part.isSalable) { // Dispose of the barcode scanner if (OneContext.hasContext) { @@ -68,23 +64,18 @@ class SOAddItemBarcodeHandler extends BarcodeHandler { L10().lineItemAdd, fields: fields, ); - } else { barcodeFailureTone(); showSnackIcon(L10().partNotSalable, success: false); } - } else { // Failed to fetch part return onBarcodeUnknown(data); } - } } - class SOAllocateStockHandler extends BarcodeHandler { - SOAllocateStockHandler({this.salesOrder, this.lineItem, this.shipment}); InvenTreeSalesOrder? salesOrder; @@ -96,10 +87,8 @@ class SOAllocateStockHandler extends BarcodeHandler { @override Future processBarcode(String barcode, - { - String url = "barcode/so-allocate/", - Map extra_data = const {}}) { - + {String url = "barcode/so-allocate/", + Map extra_data = const {}}) { final so_extra_data = { "sales_order": salesOrder?.pk, "shipment": shipment?.pk, @@ -121,8 +110,8 @@ class SOAllocateStockHandler extends BarcodeHandler { @override Future onBarcodeUnhandled(Map data) async { - - if (!data.containsKey("action_required") || !data.containsKey("line_item")) { + if (!data.containsKey("action_required") || + !data.containsKey("line_item")) { return super.onBarcodeUnhandled(data); } @@ -147,30 +136,22 @@ class SOAllocateStockHandler extends BarcodeHandler { fields["quantity"]?["value"] = data["quantity"]; fields["shipment"]?["value"] = data["shipment"]; - fields["shipment"]?["filters"] = { - "order": salesOrder!.pk.toString() - }; + fields["shipment"]?["filters"] = {"order": salesOrder!.pk.toString()}; final context = OneContext().context!; launchApiForm( - context, - L10().allocateStock, - salesOrder!.allocate_url, - fields, - method: "POST", - icon: TablerIcons.transition_right, - onSuccess: (data) async { - showSnackIcon(L10().allocated, success: true); + context, L10().allocateStock, salesOrder!.allocate_url, fields, + method: "POST", + icon: TablerIcons.transition_right, onSuccess: (data) async { + showSnackIcon(L10().allocated, success: true); }); } @override Future onBarcodeUnknown(Map data) async { barcodeFailureTone(); - showSnackIcon( - data["error"] as String? ?? L10().barcodeError, - success: false - ); + showSnackIcon(data["error"] as String? ?? L10().barcodeError, + success: false); } -} \ No newline at end of file +} diff --git a/lib/barcode/stock.dart b/lib/barcode/stock.dart index 126d1d06..79fc6967 100644 --- a/lib/barcode/stock.dart +++ b/lib/barcode/stock.dart @@ -16,7 +16,6 @@ import "package:inventree/inventree/stock.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/snacks.dart"; - /* * Generic class for scanning a StockLocation. * @@ -24,20 +23,17 @@ import "package:inventree/widget/snacks.dart"; * - Runs a "callback" function if a valid StockLocation is found */ class BarcodeScanStockLocationHandler extends BarcodeHandler { - @override String getOverlayText(BuildContext context) => L10().barcodeScanLocation; @override Future onBarcodeMatched(Map data) async { - // We expect that the barcode points to a 'stocklocation' if (data.containsKey("stocklocation")) { int _loc = (data["stocklocation"]?["pk"] ?? -1) as int; // A valid stock location! if (_loc > 0) { - debug("Scanned stock location ${_loc}"); final bool result = await onLocationScanned(_loc); @@ -64,10 +60,8 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler { // Re-implement this for particular subclass return false; } - } - /* * Generic class for scanning a StockItem * @@ -75,7 +69,6 @@ class BarcodeScanStockLocationHandler extends BarcodeHandler { * - Runs a "callback" function if a valid StockItem is found */ class BarcodeScanStockItemHandler extends BarcodeHandler { - @override String getOverlayText(BuildContext context) => L10().barcodeScanItem; @@ -87,7 +80,6 @@ class BarcodeScanStockItemHandler extends BarcodeHandler { // A valid stock location! if (_item > 0) { - barcodeSuccessTone(); bool result = await onItemScanned(_item); @@ -115,7 +107,6 @@ class BarcodeScanStockItemHandler extends BarcodeHandler { } } - /* * Barcode handler for scanning a provided StockItem into a scanned StockLocation. * @@ -124,36 +115,28 @@ class BarcodeScanStockItemHandler extends BarcodeHandler { * - The StockItem is transferred into the scanned location */ class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler { - StockItemScanIntoLocationHandler(this.item); final InvenTreeStockItem item; @override Future onLocationScanned(int locationId) async { - - final bool confirm = await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); + final bool confirm = + await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); bool result = false; if (confirm) { - Map fields = item.transferFields(); // Override location with scanned value fields["location"]?["value"] = locationId; - launchApiForm( - OneContext().context!, - L10().transferStock, - InvenTreeStockItem.transferStockUrl(), - fields, - method: "POST", - icon: TablerIcons.transfer, - onSuccess: (data) async { - showSnackIcon(L10().stockItemUpdated, success: true); - } - ); + launchApiForm(OneContext().context!, L10().transferStock, + InvenTreeStockItem.transferStockUrl(), fields, + method: "POST", icon: TablerIcons.transfer, onSuccess: (data) async { + showSnackIcon(L10().stockItemUpdated, success: true); + }); return true; } else { @@ -171,7 +154,6 @@ class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler { } } - /* * Barcode handler for scanning stock item(s) into the specified StockLocation. * @@ -180,7 +162,6 @@ class StockItemScanIntoLocationHandler extends BarcodeScanStockLocationHandler { * - The scanned StockItem is transferred into the provided StockLocation */ class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { - StockLocationScanInItemsHandler(this.location); final InvenTreeStockLocation location; @@ -190,14 +171,14 @@ class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { @override Future onItemScanned(int itemId) async { - - final InvenTreeStockItem? item = await InvenTreeStockItem().get(itemId) as InvenTreeStockItem?; - final bool confirm = await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); + final InvenTreeStockItem? item = + await InvenTreeStockItem().get(itemId) as InvenTreeStockItem?; + final bool confirm = + await InvenTreeSettingsManager().getBool(INV_STOCK_CONFIRM_SCAN, false); bool result = false; if (item != null) { - // Item is already *in* the specified location if (item.locationId == location.pk) { barcodeFailureTone(); @@ -210,27 +191,22 @@ class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { // Override location with provided location value fields["location"]?["value"] = location.pk; - launchApiForm( - OneContext().context!, - L10().transferStock, - InvenTreeStockItem.transferStockUrl(), - fields, + launchApiForm(OneContext().context!, L10().transferStock, + InvenTreeStockItem.transferStockUrl(), fields, method: "POST", - icon: TablerIcons.transfer, - onSuccess: (data) async { - showSnackIcon(L10().stockItemUpdated, success: true); - } - ); + icon: TablerIcons.transfer, onSuccess: (data) async { + showSnackIcon(L10().stockItemUpdated, success: true); + }); return true; - } else { result = await item.transferStock(location.pk); showSnackIcon( - result ? L10().barcodeScanIntoLocationSuccess : L10().barcodeScanIntoLocationFailure, - success: result - ); + result + ? L10().barcodeScanIntoLocationSuccess + : L10().barcodeScanIntoLocationFailure, + success: result); } } } @@ -240,7 +216,6 @@ class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { } } - /* * Barcode handler class for scanning a StockLocation into another StockLocation * @@ -249,14 +224,12 @@ class StockLocationScanInItemsHandler extends BarcodeScanStockItemHandler { * - The scanned StockLocation is set as the "parent" of the provided StockLocation */ class ScanParentLocationHandler extends BarcodeScanStockLocationHandler { - ScanParentLocationHandler(this.location); final InvenTreeStockLocation location; @override Future onLocationScanned(int locationId) async { - final response = await location.update( values: { "parent": locationId.toString(), @@ -269,23 +242,19 @@ class ScanParentLocationHandler extends BarcodeScanStockLocationHandler { case 201: barcodeSuccess(L10().barcodeScanIntoLocationSuccess); return true; - case 400: // Invalid parent location chosen + case 400: // Invalid parent location chosen barcodeFailureTone(); showSnackIcon(L10().invalidStockLocation, success: false); return false; default: barcodeFailureTone(); - showSnackIcon( - L10().barcodeScanIntoLocationFailure, - success: false, - actionText: L10().details, - onAction: () { - showErrorDialog( - L10().barcodeError, - response: response, - ); - } - ); + showSnackIcon(L10().barcodeScanIntoLocationFailure, + success: false, actionText: L10().details, onAction: () { + showErrorDialog( + L10().barcodeError, + response: response, + ); + }); return false; } } diff --git a/lib/barcode/tones.dart b/lib/barcode/tones.dart index a6e4b775..2f8f59f6 100644 --- a/lib/barcode/tones.dart +++ b/lib/barcode/tones.dart @@ -5,19 +5,19 @@ import "package:inventree/preferences.dart"; * Play an audible 'success' alert to the user. */ Future barcodeSuccessTone() async { - - final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool; + final bool en = await InvenTreeSettingsManager() + .getValue(INV_SOUNDS_BARCODE, true) as bool; if (en) { playAudioFile("sounds/barcode_scan.mp3"); } } -Future barcodeFailureTone() async { - - final bool en = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool; +Future barcodeFailureTone() async { + final bool en = await InvenTreeSettingsManager() + .getValue(INV_SOUNDS_BARCODE, true) as bool; if (en) { playAudioFile("sounds/barcode_error.mp3"); } -} \ No newline at end of file +} diff --git a/lib/barcode/wedge_controller.dart b/lib/barcode/wedge_controller.dart index b3147e5c..20931855 100644 --- a/lib/barcode/wedge_controller.dart +++ b/lib/barcode/wedge_controller.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -15,17 +14,14 @@ import "package:inventree/helpers.dart"; * intercepting barcode data which is entered as rapid keyboard presses */ class WedgeBarcodeController extends InvenTreeBarcodeController { - - const WedgeBarcodeController(BarcodeHandler handler, {Key? key}) : super(handler, key: key); + const WedgeBarcodeController(BarcodeHandler handler, {Key? key}) + : super(handler, key: key); @override State createState() => _WedgeBarcodeControllerState(); - } - class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { - _WedgeBarcodeControllerState() : super(); bool canScan = true; @@ -40,7 +36,6 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Future pauseScan() async { - if (mounted) { setState(() { canScan = false; @@ -50,7 +45,6 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Future resumeScan() async { - if (mounted) { setState(() { canScan = true; @@ -60,7 +54,6 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { // Callback for a single key press / scan void handleKeyEvent(KeyEvent event) { - if (!scanning) { return; } @@ -78,7 +71,8 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { DateTime now = DateTime.now(); // Throw away old characters - if (_lastScanTime == null || _lastScanTime!.isBefore(now.subtract(Duration(milliseconds: 250)))) { + if (_lastScanTime == null || + _lastScanTime!.isBefore(now.subtract(Duration(milliseconds: 250)))) { _scannedCharacters.clear(); } @@ -99,15 +93,14 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: COLOR_APP_BAR, - title: Text(L10().scanBarcode), - ), - backgroundColor: Colors.black.withValues(alpha: 0.9), - body: Center( - child: Column( + appBar: AppBar( + backgroundColor: COLOR_APP_BAR, + title: Text(L10().scanBarcode), + ), + backgroundColor: Colors.black.withValues(alpha: 0.9), + body: Center( + child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Spacer(flex: 5), @@ -118,8 +111,7 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { focusNode: _focusNode, child: SizedBox( child: CircularProgressIndicator( - color: scanning ? COLOR_ACTION : COLOR_PROGRESS - ), + color: scanning ? COLOR_ACTION : COLOR_PROGRESS), width: 64, height: 64, ), @@ -136,18 +128,12 @@ class _WedgeBarcodeControllerState extends InvenTreeBarcodeControllerState { ), Spacer(flex: 5), Padding( - child: Text( - widget.handler.getOverlayText(context), - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.white) - ), + child: Text(widget.handler.getOverlayText(context), + style: TextStyle( + fontWeight: FontWeight.bold, color: Colors.white)), padding: EdgeInsets.all(20), ) ], - ) - ) - ); + ))); } - -} \ No newline at end of file +} diff --git a/lib/dsn.dart b/lib/dsn.dart index 39092c82..87eb6eba 100644 --- a/lib/dsn.dart +++ b/lib/dsn.dart @@ -1,7 +1,7 @@ - /* * For integration with sentry.io, fill out the SENTRY_DSN_KEY value below. * This should be set to a valid DSN key, from your sentry.io account * */ -String SENTRY_DSN_KEY = "https://fea705aa4b8e4c598dcf9b146b3d1b86@o378676.ingest.sentry.io/5202450"; \ No newline at end of file +String SENTRY_DSN_KEY = + "https://fea705aa4b8e4c598dcf9b146b3d1b86@o378676.ingest.sentry.io/5202450"; diff --git a/lib/helpers.dart b/lib/helpers.dart index 349a821d..305a8106 100644 --- a/lib/helpers.dart +++ b/lib/helpers.dart @@ -17,8 +17,6 @@ import "package:audioplayers/audioplayers.dart"; import "package:inventree/l10.dart"; import "package:inventree/widget/snacks.dart"; - - List debug_messages = []; void clearDebugMessage() => debug_messages.clear(); @@ -44,14 +42,12 @@ bool debugContains(String msg, {bool raiseAssert = true}) { } if (raiseAssert) { - assert(result); } return result; } - bool isTesting() { return Platform.environment.containsKey("FLUTTER_TEST"); } @@ -64,12 +60,10 @@ bool hasContext() { } } - /* * 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")) { debug_messages.add(msg.toString()); } @@ -77,13 +71,11 @@ void debug(dynamic msg) { print("DEBUG: ${msg.toString()}"); } - /* * Simplify string representation of a floating point value * Basically, don't display fractional component if it is an integer */ String simpleNumberString(double number) { - if (number.toInt() == number) { return number.toInt().toString(); } else { @@ -98,7 +90,6 @@ String simpleNumberString(double number) { * we will not attempt to play the sound */ Future playAudioFile(String path) async { - // Debug message for unit testing debug("Playing audio file: '${path}'"); @@ -111,20 +102,17 @@ Future playAudioFile(String path) async { // Specify context options for the audio player // Ref: https://github.com/inventree/inventree-app/issues/582 player.setAudioContext(AudioContext( - android: AudioContextAndroid( - usageType: AndroidUsageType.notification, - audioFocus: AndroidAudioFocus.none, - ), - iOS: AudioContextIOS() - )); + android: AudioContextAndroid( + usageType: AndroidUsageType.notification, + audioFocus: AndroidAudioFocus.none, + ), + iOS: AudioContextIOS())); player.play(AssetSource(path)); } - // Open an external URL Future openLink(String url) async { - final link = Uri.parse(url); try { @@ -134,24 +122,20 @@ Future openLink(String url) async { } } - /* * Helper function for rendering a money / currency object as a String */ String renderCurrency(double? amount, String currency, {int decimals = 2}) { - if (amount == null || amount.isInfinite || amount.isNaN) return "-"; currency = currency.trim(); if (currency.isEmpty) return "-"; - CurrencyFormat fmt = CurrencyFormat.fromCode(currency.toLowerCase()) ?? CurrencyFormat.usd; + CurrencyFormat fmt = + CurrencyFormat.fromCode(currency.toLowerCase()) ?? CurrencyFormat.usd; - String value = CurrencyFormatter.format( - amount, - fmt - ); + String value = CurrencyFormatter.format(amount, fmt); return value; } @@ -163,8 +147,8 @@ bool isValidNumber(double? value) { /* * Render a "range" of prices between two values. */ -String formatPriceRange(double? minPrice, double? maxPrice, { String? currency }) { - +String formatPriceRange(double? minPrice, double? maxPrice, + {String? currency}) { // Account for empty or null values if (!isValidNumber(minPrice) && !isValidNumber(maxPrice)) { return "-"; diff --git a/lib/inventree/bom.dart b/lib/inventree/bom.dart index 8019ecf0..7c89376d 100644 --- a/lib/inventree/bom.dart +++ b/lib/inventree/bom.dart @@ -1,4 +1,3 @@ - import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/part.dart"; @@ -6,13 +5,13 @@ import "package:inventree/inventree/part.dart"; * Class representing the BomItem database model */ class InvenTreeBomItem extends InvenTreeModel { - InvenTreeBomItem() : super(); InvenTreeBomItem.fromJson(Map json) : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeBomItem.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeBomItem.fromJson(json); @override String get URL => "bom/"; @@ -28,7 +27,7 @@ class InvenTreeBomItem extends InvenTreeModel { // Extract the 'reference' value associated with this BomItem String get reference => getString("reference"); - + // Extract the 'quantity' value associated with this BomItem double get quantity => getDouble("quantity"); @@ -57,8 +56,8 @@ class InvenTreeBomItem extends InvenTreeModel { } return null; -} + } // Extract the ID of the related sub-part int get subPartId => getInt("sub_part"); -} \ No newline at end of file +} diff --git a/lib/inventree/company.dart b/lib/inventree/company.dart index 072f0fe2..312b5367 100644 --- a/lib/inventree/company.dart +++ b/lib/inventree/company.dart @@ -6,13 +6,11 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/purchase_order.dart"; import "package:inventree/widget/company/company_detail.dart"; - /* * The InvenTreeCompany class represents the Company model in the InvenTree database. */ class InvenTreeCompany extends InvenTreeModel { - InvenTreeCompany() : super(); InvenTreeCompany.fromJson(Map json) : super.fromJson(json); @@ -24,16 +22,13 @@ class InvenTreeCompany extends InvenTreeModel { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CompanyDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => CompanyDetailWidget(this))); } @override - List get rolesRequired => ["purchase_order", "sales_order", "return_order"]; + List get rolesRequired => + ["purchase_order", "sales_order", "return_order"]; @override Map> formFields() { @@ -54,12 +49,16 @@ class InvenTreeCompany extends InvenTreeModel { return fields; } - String get image => (jsondata["image"] ?? jsondata["thumbnail"] ?? InvenTreeAPI.staticImage) as String; + String get image => + (jsondata["image"] ?? jsondata["thumbnail"] ?? InvenTreeAPI.staticImage) + as String; - String get thumbnail => (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) as String; + String get thumbnail => + (jsondata["thumbnail"] ?? jsondata["image"] ?? InvenTreeAPI.staticThumb) + as String; String get website => getString("website"); - + String get phone => getString("phone"); String get email => getString("email"); @@ -73,23 +72,20 @@ class InvenTreeCompany extends InvenTreeModel { bool get active => getBool("active", backup: true); int get partSuppliedCount => getInt("part_supplied"); - - int get partManufacturedCount => getInt("parts_manufactured"); - - // Request a list of purchase orders against this company - Future> getPurchaseOrders({bool? outstanding}) async { - Map filters = { - "supplier": "${pk}" - }; + int get partManufacturedCount => getInt("parts_manufactured"); + + // Request a list of purchase orders against this company + Future> getPurchaseOrders( + {bool? outstanding}) async { + Map filters = {"supplier": "${pk}"}; if (outstanding != null) { filters["outstanding"] = outstanding ? "true" : "false"; } - final List results = await InvenTreePurchaseOrder().list( - filters: filters - ); + final List results = + await InvenTreePurchaseOrder().list(filters: filters); List orders = []; @@ -103,18 +99,18 @@ class InvenTreeCompany extends InvenTreeModel { } @override - InvenTreeModel createFromJson(Map json) => InvenTreeCompany.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeCompany.fromJson(json); } - /* * Class representing an attachment file against a Company object */ class InvenTreeCompanyAttachment extends InvenTreeAttachment { - InvenTreeCompanyAttachment() : super(); - InvenTreeCompanyAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreeCompanyAttachment.fromJson(Map json) + : super.fromJson(json); @override String get REFERENCE_FIELD => "company"; @@ -123,21 +119,23 @@ class InvenTreeCompanyAttachment extends InvenTreeAttachment { String get REF_MODEL_TYPE => "company"; @override - String get URL => InvenTreeAPI().supportsModernAttachments ? "attachment/" : "company/attachment/"; + String get URL => InvenTreeAPI().supportsModernAttachments + ? "attachment/" + : "company/attachment/"; @override - InvenTreeModel createFromJson(Map json) => InvenTreeCompanyAttachment.fromJson(json); - + InvenTreeModel createFromJson(Map json) => + InvenTreeCompanyAttachment.fromJson(json); } /* * The InvenTreeSupplierPart class represents the SupplierPart model in the InvenTree database */ class InvenTreeSupplierPart extends InvenTreeModel { - InvenTreeSupplierPart() : super(); - InvenTreeSupplierPart.fromJson(Map json) : super.fromJson(json); + InvenTreeSupplierPart.fromJson(Map json) + : super.fromJson(json); @override String get URL => "company/part/"; @@ -180,37 +178,43 @@ class InvenTreeSupplierPart extends InvenTreeModel { }; } - int get manufacturerId => getInt("pk", subKey: "manufacturer_detail"); - - String get manufacturerName => getString("name", subKey: "manufacturer_detail"); - + + String get manufacturerName => + getString("name", subKey: "manufacturer_detail"); + String get MPN => getString("MPN", subKey: "manufacturer_part_detail"); - - String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + + String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? + jsondata["manufacturer_detail"]?["thumbnail"] ?? + InvenTreeAPI.staticThumb) as String; int get manufacturerPartId => getInt("manufacturer_part"); - + int get supplierId => getInt("supplier"); - + String get supplierName => getString("name", subKey: "supplier_detail"); - - String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? jsondata["supplier_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + + String get supplierImage => (jsondata["supplier_detail"]?["image"] ?? + jsondata["supplier_detail"]?["thumbnail"] ?? + InvenTreeAPI.staticThumb) as String; String get SKU => getString("SKU"); bool get active => getBool("active", backup: true); - + int get partId => getInt("part"); - String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get partImage => + (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) + as String; String get partName => getString("name", subKey: "part_detail"); Map get partDetail => getMap("part_detail"); String get partDescription => getString("description", subKey: "part_detail"); - + String get note => getString("note"); String get packaging => getString("packaging"); @@ -224,15 +228,15 @@ class InvenTreeSupplierPart extends InvenTreeModel { } @override - InvenTreeModel createFromJson(Map json) => InvenTreeSupplierPart.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSupplierPart.fromJson(json); } - class InvenTreeManufacturerPart extends InvenTreeModel { - InvenTreeManufacturerPart() : super(); - InvenTreeManufacturerPart.fromJson(Map json) : super.fromJson(json); + InvenTreeManufacturerPart.fromJson(Map json) + : super.fromJson(json); @override String URL = "company/part/manufacturer/"; @@ -269,18 +273,25 @@ class InvenTreeManufacturerPart extends InvenTreeModel { String get partIPN => getString("IPN", subKey: "part_detail"); - String get partImage => (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get partImage => + (jsondata["part_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) + as String; int get manufacturerId => getInt("manufacturer"); - String get manufacturerName => getString("name", subKey: "manufacturer_detail"); + String get manufacturerName => + getString("name", subKey: "manufacturer_detail"); - String get manufacturerDescription => getString("description", subKey: "manufacturer_detail"); + String get manufacturerDescription => + getString("description", subKey: "manufacturer_detail"); - String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? jsondata["manufacturer_detail"]?["thumbnail"] ?? InvenTreeAPI.staticThumb) as String; + String get manufacturerImage => (jsondata["manufacturer_detail"]?["image"] ?? + jsondata["manufacturer_detail"]?["thumbnail"] ?? + InvenTreeAPI.staticThumb) as String; String get MPN => getString("MPN"); @override - InvenTreeModel createFromJson(Map json) => InvenTreeManufacturerPart.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeManufacturerPart.fromJson(json); } diff --git a/lib/inventree/model.dart b/lib/inventree/model.dart index 07b74f83..1245428e 100644 --- a/lib/inventree/model.dart +++ b/lib/inventree/model.dart @@ -17,10 +17,8 @@ import "package:inventree/inventree/sentry.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/fields.dart"; - // Paginated response object class InvenTreePageResponse { - InvenTreePageResponse() { results = []; } @@ -31,7 +29,7 @@ class InvenTreePageResponse { // Total number of results in the dataset int count = 0; - + int get length => results.length; List results = []; @@ -42,7 +40,6 @@ class InvenTreePageResponse { * for interacting with InvenTree data. */ class InvenTreeModel { - InvenTreeModel(); // Construct an InvenTreeModel from a JSON data object @@ -87,7 +84,6 @@ class InvenTreeModel { // If a subKey is specified, we need to dig deeper into the JSON data if (subKey.isNotEmpty) { - if (!data.containsKey(subKey)) { debug("JSON data does not contain subKey '$subKey' for key '$key'"); return backup; @@ -98,7 +94,6 @@ class InvenTreeModel { if (sub_data is Map) { data = (data[subKey] ?? {}) as Map; } - } if (data.containsKey(key)) { @@ -109,7 +104,8 @@ class InvenTreeModel { } // Helper function to get sub-map from JSON data - Map getMap(String key, {Map backup = const {}, String subKey = ""}) { + Map getMap(String key, + {Map backup = const {}, String subKey = ""}) { dynamic value = getValue(key, backup: backup, subKey: subKey); if (value == null) { @@ -152,7 +148,7 @@ class InvenTreeModel { return double.tryParse(value.toString()) ?? backup; } - double getDouble(String key, {double backup = 0.0, String subkey = "" }) { + double getDouble(String key, {double backup = 0.0, String subkey = ""}) { double? value = getDoubleOrNull(key, backup: backup, subKey: subkey); return value ?? backup; } @@ -194,7 +190,6 @@ class InvenTreeModel { // Return the InvenTree web server URL for this object String get webUrl { - if (api.isConnected()) { String web = InvenTreeAPI().baseUrl; @@ -205,7 +200,6 @@ class InvenTreeModel { web = web.replaceAll("//", "/"); return web; - } else { return ""; } @@ -216,7 +210,8 @@ class InvenTreeModel { */ List get rolesRequired { // Default implementation should not be called - debug("rolesRequired() not implemented for model ${URL} - returning empty list"); + debug( + "rolesRequired() not implemented for model ${URL} - returning empty list"); return []; } @@ -271,12 +266,14 @@ class InvenTreeModel { // Fields for editing / creating this model // Override per-model Map> formFields() { - return {}; } - Future createForm(BuildContext context, String title, {String fileField = "", Map fields=const{}, Map data=const {}, Function(dynamic)? onSuccess}) async { - + Future createForm(BuildContext context, String title, + {String fileField = "", + Map fields = const {}, + Map data = const {}, + Function(dynamic)? onSuccess}) async { if (fields.isEmpty) { fields = formFields(); } @@ -291,28 +288,20 @@ class InvenTreeModel { method: "POST", fileField: fileField, ); - } /* * Launch a modal form to edit the fields available to this model instance. */ - Future editForm(BuildContext context, String title, {Map fields=const {}, Function(dynamic)? onSuccess}) async { - + Future editForm(BuildContext context, String title, + {Map fields = const {}, + Function(dynamic)? onSuccess}) async { if (fields.isEmpty) { fields = formFields(); } - launchApiForm( - context, - title, - url, - fields, - modelData: jsondata, - onSuccess: onSuccess, - method: "PATCH" - ); - + launchApiForm(context, title, url, fields, + modelData: jsondata, onSuccess: onSuccess, method: "PATCH"); } // JSON data which defines this object @@ -324,12 +313,12 @@ class InvenTreeModel { int get pk => getInt("pk"); String get pkString => pk.toString(); - + // Some common accessors String get name => getString("name"); String get description => getString("description"); - + String get notes => getString("notes"); int get parentId => getInt("parent"); @@ -387,8 +376,7 @@ class InvenTreeModel { return ""; } - Future goToInvenTreePage() async { - + Future goToInvenTreePage() async { var uri = Uri.tryParse(webUrl); if (uri != null && await canLaunchUrl(uri)) { await launchUrl(uri); @@ -397,8 +385,7 @@ class InvenTreeModel { } } - Future openLink() async { - + Future openLink() async { if (link.isNotEmpty) { var uri = Uri.tryParse(link); if (uri != null && await canLaunchUrl(uri)) { @@ -408,16 +395,19 @@ class InvenTreeModel { } String get keywords => getString("keywords"); - + // Create a new object from JSON data (not a constructor!) - InvenTreeModel createFromJson(Map json) => InvenTreeModel.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeModel.fromJson(json); // Return the API detail endpoint for this Model object String get url => "${URL}/${pk}/".replaceAll("//", "/"); // Search this Model type in the database - Future> search(String searchTerm, {Map filters = const {}, int offset = 0, int limit = 25}) async { - + Future> search(String searchTerm, + {Map filters = const {}, + int offset = 0, + int limit = 25}) async { Map searchFilters = {}; for (String key in filters.keys) { @@ -431,12 +421,11 @@ class InvenTreeModel { final results = list(filters: searchFilters); return results; - } // Return the number of results that would meet a particular "query" - Future count({Map filters = const {}, String searchQuery = ""} ) async { - + Future count( + {Map filters = const {}, String searchQuery = ""}) async { var params = defaultListFilters(); filters.forEach((String key, String value) { @@ -458,7 +447,7 @@ class InvenTreeModel { } else { return 0; } -} + } Map defaultFilters() { return {}; @@ -476,8 +465,8 @@ class InvenTreeModel { /* * Report error information to sentry, when a model operation fails. */ - Future reportModelError(String title, APIResponse response, {Map context = const {}}) async { - + Future reportModelError(String title, APIResponse response, + {Map context = const {}}) async { String dataString = response.data?.toString() ?? "null"; // If the response has "errorDetail" set, then the error has already been handled, and there is no need to continue @@ -515,7 +504,6 @@ class InvenTreeModel { /// Delete the instance on the remote server /// Returns true if the operation was successful, else false Future delete() async { - // Return if we do not have a valid pk if (pk < 0) { return false; @@ -523,8 +511,9 @@ class InvenTreeModel { var response = await api.delete(url); - if (!response.isValid() || response.data == null || (response.data is! Map)) { - + if (!response.isValid() || + response.data == null || + (response.data is! Map)) { reportModelError( "InvenTreeModel.delete() returned invalid response", response, @@ -547,26 +536,23 @@ class InvenTreeModel { * Reload this object, by requesting data from the server */ Future reload() async { - // If we do not have a valid pk (for some reason), exit immediately if (pk < 0) { return false; } - var response = await api.get(url, params: defaultGetFilters(), expectedStatusCode: 200); + var response = await api.get(url, + params: defaultGetFilters(), expectedStatusCode: 200); // A valid response has been returned if (response.isValid() && response.statusCode == 200) { - // Returned data was not a valid JSON object if (response.data == null || response.data is! Map) { reportModelError( - "InvenTreeModel.reload() returned invalid response", - response, + "InvenTreeModel.reload() returned invalid response", response, context: { "pk": pk.toString(), - } - ); + }); showServerError( url, @@ -577,7 +563,6 @@ class InvenTreeModel { return false; } } else { - switch (response.statusCode) { case 404: // Object has been deleted showSnackIcon( @@ -589,11 +574,7 @@ class InvenTreeModel { String detail = L10().errorFetch; detail += "\n${L10().statusCode}: ${response.statusCode}"; - showServerError( - url, - L10().serverError, - detail - ); + showServerError(url, L10().serverError, detail); break; } @@ -608,8 +589,9 @@ class InvenTreeModel { } // POST data to update the model - Future update({Map values = const {}, int? expectedStatusCode = 200}) async { - + Future update( + {Map values = const {}, + int? expectedStatusCode = 200}) async { var url = path.join(URL, pk.toString()); // Return if we do not have a valid pk @@ -633,8 +615,8 @@ class InvenTreeModel { } // Return the detail view for the associated pk - Future getModel(String pk, {Map filters = const {}}) async { - + Future getModel(String pk, + {Map filters = const {}}) async { var url = path.join(URL, pk.toString()); if (!url.endsWith("/")) { @@ -651,17 +633,14 @@ class InvenTreeModel { var response = await api.get(url, params: params); if (!response.isValid() || response.data == null || response.data is! Map) { - if (response.statusCode != -1) { // Report error reportModelError( - "InvenTreeModel.getModel() returned invalid response", - response, + "InvenTreeModel.getModel() returned invalid response", response, context: { "filters": filters.toString(), "pk": pk, - } - ); + }); } showServerError( @@ -671,7 +650,6 @@ class InvenTreeModel { ); return null; - } lastReload = DateTime.now(); @@ -679,8 +657,8 @@ class InvenTreeModel { return createFromJson(response.asMap()); } - Future get(int pk, {Map filters = const {}}) async { - + Future get(int pk, + {Map filters = const {}}) async { if (pk < 0) { return null; } @@ -689,7 +667,6 @@ class InvenTreeModel { } Future create(Map data) async { - if (data.containsKey("pk")) { data.remove("pk"); } @@ -702,14 +679,11 @@ class InvenTreeModel { // Invalid response returned from server if (!response.isValid() || response.data == null || response.data is! Map) { - reportModelError( - "InvenTreeModel.create() returned invalid response", - response, + "InvenTreeModel.create() returned invalid response", response, context: { "pk": pk.toString(), - } - ); + }); showServerError( URL, @@ -723,7 +697,8 @@ class InvenTreeModel { return createFromJson(response.asMap()); } - Future listPaginated(int limit, int offset, {Map filters = const {}}) async { + Future listPaginated(int limit, int offset, + {Map filters = const {}}) async { var params = defaultListFilters(); for (String key in filters.keys) { @@ -739,7 +714,6 @@ class InvenTreeModel { * - In such a case, we want to concatenate them together */ if (params.containsKey("original_search")) { - String search = params["search"] ?? ""; String original = params["original_search"] ?? ""; @@ -761,18 +735,20 @@ class InvenTreeModel { // First attempt is to look for paginated data, returned as a map - if (dataMap.isNotEmpty && dataMap.containsKey("count") && dataMap.containsKey("results")) { + if (dataMap.isNotEmpty && + dataMap.containsKey("count") && + dataMap.containsKey("results")) { page.count = (dataMap["count"] ?? 0) as int; - page.results = []; + page.results = []; - List results = dataMap["results"] as List; + List results = dataMap["results"] as List; - for (dynamic result in results) { - page.addResult(createFromJson(result as Map)); - } + for (dynamic result in results) { + page.addResult(createFromJson(result as Map)); + } - return page; + return page; } // Second attempt is to look for a list of data (not paginated) @@ -784,7 +760,7 @@ class InvenTreeModel { for (var result in dataList) { page.addResult(createFromJson(result as Map)); - } + } return page; } @@ -794,7 +770,8 @@ class InvenTreeModel { } // Return list of objects from the database, with optional filters - Future> list({Map filters = const {}}) async { + Future> list( + {Map filters = const {}}) async { var params = defaultListFilters(); for (String key in filters.keys) { @@ -823,7 +800,6 @@ class InvenTreeModel { } for (var d in data) { - // Create a new object (of the current class type InvenTreeModel obj = createFromJson(d as Map); @@ -849,7 +825,6 @@ class InvenTreeModel { // Each filter must be matched // Used for (e.g.) filtering returned results bool filter(String filterString) { - List filters = filterString.trim().toLowerCase().split(" "); for (var f in filters) { @@ -862,22 +837,20 @@ class InvenTreeModel { } } - /* * Class representing a single plugin instance */ class InvenTreePlugin extends InvenTreeModel { - InvenTreePlugin() : super(); InvenTreePlugin.fromJson(Map json) : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreePlugin.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePlugin.fromJson(json); @override String get URL { - /* Note: The plugin API endpoint changed at API version 90, * < 90 = 'plugin' * >= 90 = 'plugins' @@ -891,23 +864,24 @@ class InvenTreePlugin extends InvenTreeModel { } String get key => getString("key"); - + bool get active => getBool("active"); - + // Return the metadata struct for this plugin - Map get _meta => (jsondata["meta"] ?? {}) as Map; + Map get _meta => + (jsondata["meta"] ?? {}) as Map; String get humanName => (_meta["human_name"] ?? "") as String; // Return the mixins struct for this plugin - Map get _mixins => (jsondata["mixins"] ?? {}) as Map; + Map get _mixins => + (jsondata["mixins"] ?? {}) as Map; bool supportsMixin(String mixin) { return _mixins.containsKey(mixin); } } - /* * Class representing a 'setting' object on the InvenTree server. * There are two sorts of settings available from the server, via the API: @@ -915,10 +889,10 @@ class InvenTreePlugin extends InvenTreeModel { * - UserSetting (applicable only to the current user) */ class InvenTreeGlobalSetting extends InvenTreeModel { - InvenTreeGlobalSetting() : super(); - InvenTreeGlobalSetting.fromJson(Map json) : super.fromJson(json); + InvenTreeGlobalSetting.fromJson(Map json) + : super.fromJson(json); @override InvenTreeGlobalSetting createFromJson(Map json) { @@ -929,18 +903,17 @@ class InvenTreeGlobalSetting extends InvenTreeModel { String get URL => "settings/global/"; String get key => getString("key"); - - String get value => getString("value"); - - String get type => getString("type"); + String get value => getString("value"); + + String get type => getString("type"); } class InvenTreeUserSetting extends InvenTreeGlobalSetting { - InvenTreeUserSetting() : super(); - InvenTreeUserSetting.fromJson(Map json) : super.fromJson(json); + InvenTreeUserSetting.fromJson(Map json) + : super.fromJson(json); @override InvenTreeGlobalSetting createFromJson(Map json) { @@ -951,22 +924,19 @@ class InvenTreeUserSetting extends InvenTreeGlobalSetting { String get URL => "settings/user/"; } - class InvenTreeAttachment extends InvenTreeModel { // Class representing an "attachment" file InvenTreeAttachment() : super(); - InvenTreeAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreeAttachment.fromJson(Map json) + : super.fromJson(json); @override String get URL => "attachment/"; @override Map> formFields() { - Map> fields = { - "link": {}, - "comment": {} - }; + Map> fields = {"link": {}, "comment": {}}; if (!hasLink) { fields.remove("link"); @@ -1024,7 +994,7 @@ class InvenTreeAttachment extends InvenTreeModel { } String get comment => getString("comment"); - + DateTime? get uploadDate { if (jsondata.containsKey("upload_date")) { return DateTime.tryParse((jsondata["upload_date"] ?? "") as String); @@ -1035,7 +1005,6 @@ class InvenTreeAttachment extends InvenTreeModel { // Return a count of how many attachments exist against the specified model ID Future countAttachments(int modelId) { - Map filters = {}; if (InvenTreeAPI().supportsModernAttachments) { @@ -1048,8 +1017,8 @@ class InvenTreeAttachment extends InvenTreeModel { return count(filters: filters); } - Future uploadAttachment(File attachment, int modelId, {String comment = "", Map fields = const {}}) async { - + Future uploadAttachment(File attachment, int modelId, + {String comment = "", Map fields = const {}}) async { // Ensure that the correct reference field is set Map data = Map.from(fields); @@ -1060,15 +1029,13 @@ class InvenTreeAttachment extends InvenTreeModel { } if (InvenTreeAPI().supportsModernAttachments) { - url = "attachment/"; data["model_id"] = modelId.toString(); data["model_type"] = REF_MODEL_TYPE; - } else { - if (REFERENCE_FIELD.isEmpty) { - sentryReportMessage("uploadAttachment called with empty 'REFERENCE_FIELD'"); + sentryReportMessage( + "uploadAttachment called with empty 'REFERENCE_FIELD'"); return false; } @@ -1076,24 +1043,17 @@ class InvenTreeAttachment extends InvenTreeModel { } final APIResponse response = await InvenTreeAPI().uploadFile( - url, - attachment, - method: "POST", - name: "attachment", - fields: data - ); + url, attachment, + method: "POST", name: "attachment", fields: data); return response.successful(); } - Future uploadImage(int modelId, {String prefix = "InvenTree"}) async { - bool result = false; await FilePickerDialog.pickImageFromCamera().then((File? file) { if (file != null) { - String dir = path.dirname(file.path); String ext = path.extension(file.path); String now = DateTime.now().toIso8601String().replaceAll(":", "-"); @@ -1120,14 +1080,10 @@ class InvenTreeAttachment extends InvenTreeModel { return result; } - /* * Download this attachment file */ Future downloadAttachment() async { await InvenTreeAPI().downloadFile(attachment); } - } - - diff --git a/lib/inventree/notification.dart b/lib/inventree/notification.dart index ae79fa2c..6dbbfda6 100644 --- a/lib/inventree/notification.dart +++ b/lib/inventree/notification.dart @@ -5,10 +5,10 @@ import "package:inventree/inventree/model.dart"; */ class InvenTreeNotification extends InvenTreeModel { - InvenTreeNotification() : super(); - InvenTreeNotification.fromJson(Map json) : super.fromJson(json); + InvenTreeNotification.fromJson(Map json) + : super.fromJson(json); @override InvenTreeNotification createFromJson(Map json) { @@ -20,7 +20,6 @@ class InvenTreeNotification extends InvenTreeModel { @override Map defaultListFilters() { - // By default, only return 'unread' notifications return { "read": "false", @@ -28,7 +27,7 @@ class InvenTreeNotification extends InvenTreeModel { } String get message => getString("message"); - + DateTime? get creationDate { if (jsondata.containsKey("creation")) { return DateTime.tryParse((jsondata["creation"] ?? "") as String); @@ -41,7 +40,6 @@ class InvenTreeNotification extends InvenTreeModel { * Dismiss this notification (mark as read) */ Future dismiss() async { - if (api.apiVersion >= 82) { // "Modern" API endpoint operates a little differently await update(values: {"read": "true"}); @@ -49,5 +47,4 @@ class InvenTreeNotification extends InvenTreeModel { await api.post("${url}read/"); } } - -} \ No newline at end of file +} diff --git a/lib/inventree/orders.dart b/lib/inventree/orders.dart index d06ddc86..a28a4f25 100644 --- a/lib/inventree/orders.dart +++ b/lib/inventree/orders.dart @@ -2,16 +2,13 @@ * Base model for various "orders" which share common properties */ - import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/part.dart"; - /* * Generic class representing an "order" */ class InvenTreeOrder extends InvenTreeModel { - InvenTreeOrder() : super(); InvenTreeOrder.fromJson(Map json) : super.fromJson(json); @@ -34,7 +31,8 @@ class InvenTreeOrder extends InvenTreeModel { int get shipmentCount => getInt("shipments_count", backup: 0); - int get completedShipmentCount => getInt("completed_shipments_count", backup: 0); + int get completedShipmentCount => + getInt("completed_shipments_count", backup: 0); bool get complete => completedLineItemCount >= lineItemCount; @@ -46,14 +44,16 @@ class InvenTreeOrder extends InvenTreeModel { String get responsibleName => getString("name", subKey: "responsible_detail"); - String get responsibleLabel => getString("label", subKey: "responsible_detail"); + String get responsibleLabel => + getString("label", subKey: "responsible_detail"); // Project code information int get projectCodeId => getInt("project_code"); String get projectCode => getString("code", subKey: "project_code_detail"); - String get projectCodeDescription => getString("description", subKey: "project_code_detail"); + String get projectCodeDescription => + getString("description", subKey: "project_code_detail"); bool get hasProjectCode => projectCode.isNotEmpty; @@ -84,12 +84,10 @@ class InvenTreeOrder extends InvenTreeModel { } } - /* * Generic class representing an "order line" */ class InvenTreeOrderLine extends InvenTreeModel { - InvenTreeOrderLine() : super(); InvenTreeOrderLine.fromJson(Map json) : super.fromJson(json); @@ -121,15 +119,14 @@ class InvenTreeOrderLine extends InvenTreeModel { String get targetDate => getDateString("target_date"); } - /* * Generic class representing an "ExtraLineItem" */ class InvenTreeExtraLineItem extends InvenTreeModel { - InvenTreeExtraLineItem() : super(); - InvenTreeExtraLineItem.fromJson(Map json) : super.fromJson(json); + InvenTreeExtraLineItem.fromJson(Map json) + : super.fromJson(json); int get orderId => getInt("order"); @@ -157,5 +154,4 @@ class InvenTreeExtraLineItem extends InvenTreeModel { "notes": {}, }; } - -} \ No newline at end of file +} diff --git a/lib/inventree/part.dart b/lib/inventree/part.dart index 0aeec560..cdf93d62 100644 --- a/lib/inventree/part.dart +++ b/lib/inventree/part.dart @@ -14,15 +14,14 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/widget/part/category_display.dart"; import "package:inventree/widget/part/part_detail.dart"; - /* * Class representing the PartCategory database model */ class InvenTreePartCategory extends InvenTreeModel { - InvenTreePartCategory() : super(); - InvenTreePartCategory.fromJson(Map json) : super.fromJson(json); + InvenTreePartCategory.fromJson(Map json) + : super.fromJson(json); @override String get URL => "part/category/"; @@ -36,17 +35,12 @@ class InvenTreePartCategory extends InvenTreeModel { @override Future goToDetailPage(BuildContext context) async { // Default implementation does not do anything... - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => CategoryDisplayWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => CategoryDisplayWidget(this))); } @override Map> formFields() { - Map> fields = { "name": {}, "description": {}, @@ -58,9 +52,8 @@ class InvenTreePartCategory extends InvenTreeModel { } String get pathstring => getString("pathstring"); - - String get parentPathString { + String get parentPathString { List psplit = pathstring.split("/"); if (psplit.isNotEmpty) { @@ -78,21 +71,22 @@ class InvenTreePartCategory extends InvenTreeModel { // Return the number of parts in this category // Note that the API changed from 'parts' to 'part_count' (v69) - int get partcount => (jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int; + int get partcount => + (jsondata["part_count"] ?? jsondata["parts"] ?? 0) as int; @override - InvenTreeModel createFromJson(Map json) => InvenTreePartCategory.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePartCategory.fromJson(json); } - /* * Class representing the PartTestTemplate database model */ class InvenTreePartTestTemplate extends InvenTreeModel { - InvenTreePartTestTemplate() : super(); - InvenTreePartTestTemplate.fromJson(Map json) : super.fromJson(json); + InvenTreePartTestTemplate.fromJson(Map json) + : super.fromJson(json); @override String get URL => "part/test-template/"; @@ -104,16 +98,16 @@ class InvenTreePartTestTemplate extends InvenTreeModel { String get testName => getString("test_name"); bool get required => getBool("required"); - + bool get requiresValue => getBool("requires_value"); bool get requiresAttachment => getBool("requires_attachment"); @override - InvenTreeModel createFromJson(Map json) => InvenTreePartTestTemplate.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePartTestTemplate.fromJson(json); bool passFailStatus() { - var result = latestResult(); if (result == null) { @@ -134,17 +128,16 @@ class InvenTreePartTestTemplate extends InvenTreeModel { return results.last; } - } /* Class representing the PartParameter database model */ class InvenTreePartParameter extends InvenTreeModel { - InvenTreePartParameter() : super(); - InvenTreePartParameter.fromJson(Map json) : super.fromJson(json); + InvenTreePartParameter.fromJson(Map json) + : super.fromJson(json); @override String get URL => "part/parameter/"; @@ -153,11 +146,11 @@ class InvenTreePartParameter extends InvenTreeModel { List get rolesRequired => ["part"]; @override - InvenTreeModel createFromJson(Map json) => InvenTreePartParameter.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePartParameter.fromJson(json); @override Map> formFields() { - Map> fields = { "header": { "type": "string", @@ -179,9 +172,9 @@ class InvenTreePartParameter extends InvenTreeModel { @override String get description => getString("description", subKey: "template_detail"); - + String get value => getString("data"); - + String get valueString { String v = value; @@ -196,15 +189,15 @@ class InvenTreePartParameter extends InvenTreeModel { bool get as_bool => value.toLowerCase() == "true"; String get units => getString("units", subKey: "template_detail"); - - bool get is_checkbox => getBool("checkbox", subKey: "template_detail", backup: false); + + bool get is_checkbox => + getBool("checkbox", subKey: "template_detail", backup: false); } /* * Class representing the Part database model */ class InvenTreePart extends InvenTreeModel { - InvenTreePart() : super(); InvenTreePart.fromJson(Map json) : super.fromJson(json); @@ -221,12 +214,8 @@ class InvenTreePart extends InvenTreeModel { @override Future goToDetailPage(BuildContext context) async { // Default implementation does not do anything... - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PartDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => PartDetailWidget(this))); } @override @@ -270,8 +259,8 @@ class InvenTreePart extends InvenTreeModel { int get stockItemCount => stockItems.length; // Request stock items for this part - Future getStockItems(BuildContext context, {bool showDialog=false}) async { - + Future getStockItems(BuildContext context, + {bool showDialog = false}) async { await InvenTreeStockItem().list( filters: { "part": "${pk}", @@ -290,7 +279,6 @@ class InvenTreePart extends InvenTreeModel { // Request pricing data for this part Future getPricing() async { - print("REQUEST PRICING FOR: ${pk}"); try { @@ -311,16 +299,14 @@ class InvenTreePart extends InvenTreeModel { } int get supplierCount => getInt("suppliers", backup: 0); - + // Request supplier parts for this part Future> getSupplierParts() async { List _supplierParts = []; - final parts = await InvenTreeSupplierPart().list( - filters: { - "part": "${pk}", - } - ); + final parts = await InvenTreeSupplierPart().list(filters: { + "part": "${pk}", + }); for (var result in parts) { if (result is InvenTreeSupplierPart) { @@ -338,13 +324,11 @@ class InvenTreePart extends InvenTreeModel { // Request test templates from the serve Future getTestTemplates() async { - InvenTreePartTestTemplate().list( filters: { "part": "${pk}", }, ).then((var templates) { - testingTemplates.clear(); for (var t in templates) { @@ -373,12 +357,12 @@ class InvenTreePart extends InvenTreeModel { // Get the 'available stock' for this Part double get unallocatedStock { - double unallocated = 0; // Note that the 'available_stock' was not added until API v35 if (jsondata.containsKey("unallocated_stock")) { - unallocated = double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0; + unallocated = + double.tryParse(jsondata["unallocated_stock"].toString()) ?? 0; } else { unallocated = inStock; } @@ -386,148 +370,150 @@ class InvenTreePart extends InvenTreeModel { return max(0, unallocated); } - String get unallocatedStockString => simpleNumberString(unallocatedStock); + String get unallocatedStockString => simpleNumberString(unallocatedStock); - String stockString({bool includeUnits = true}) { - String q = unallocatedStockString; + String stockString({bool includeUnits = true}) { + String q = unallocatedStockString; - if (unallocatedStock != inStock) { - q += " / ${inStockString}"; - } - - if (includeUnits && units.isNotEmpty) { - q += " ${units}"; - } - - return q; + if (unallocatedStock != inStock) { + q += " / ${inStockString}"; } - String get units => getString("units"); - - // Get the ID of the Part that this part is a variant of (or null) - int? get variantOf => jsondata["variant_of"] as int?; - - // Get the number of units being build for this Part - double get building => getDouble("building"); - - // Get the number of BOMs this Part is used in (if it is a component) - int get usedInCount => jsondata.containsKey("used_in") ? getInt("used_in", backup: 0) : 0; - - bool get isAssembly => getBool("assembly"); - - bool get isComponent => getBool("component"); - - bool get isPurchaseable => getBool("purchaseable"); - - bool get isSalable => getBool("salable"); - - bool get isActive => getBool("active"); - - bool get isVirtual => getBool("virtual"); - - bool get isTemplate => getBool("is_template"); - - bool get isTrackable => getBool("trackable"); - - // Get the IPN (internal part number) for the Part instance - String get IPN => getString("IPN"); - - // Get the revision string for the Part instance - String get revision => getString("revision"); - - // Get the category ID for the Part instance (or "null" if does not exist) - int get categoryId => getInt("category"); - - // Get the category name for the Part instance - String get categoryName { - // Inavlid category ID - if (categoryId <= 0) return ""; - - if (!jsondata.containsKey("category_detail")) return ""; - - return (jsondata["category_detail"]?["name"] ?? "") as String; + if (includeUnits && units.isNotEmpty) { + q += " ${units}"; } - // Get the category description for the Part instance - String get categoryDescription { - // Invalid category ID - if (categoryId <= 0) return ""; + return q; + } - if (!jsondata.containsKey("category_detail")) return ""; + String get units => getString("units"); - return (jsondata["category_detail"]?["description"] ?? "") as String; - } - // Get the image URL for the Part instance - String get _image => getString("image"); + // Get the ID of the Part that this part is a variant of (or null) + int? get variantOf => jsondata["variant_of"] as int?; - // Get the thumbnail URL for the Part instance - String get _thumbnail => getString("thumbnail"); + // Get the number of units being build for this Part + double get building => getDouble("building"); - // Return the fully-qualified name for the Part instance - String get fullname { + // Get the number of BOMs this Part is used in (if it is a component) + int get usedInCount => + jsondata.containsKey("used_in") ? getInt("used_in", backup: 0) : 0; - String fn = getString("full_name"); + bool get isAssembly => getBool("assembly"); - if (fn.isNotEmpty) return fn; + bool get isComponent => getBool("component"); - List elements = []; + bool get isPurchaseable => getBool("purchaseable"); - if (IPN.isNotEmpty) elements.add(IPN); + bool get isSalable => getBool("salable"); - elements.add(name); + bool get isActive => getBool("active"); - if (revision.isNotEmpty) elements.add(revision); + bool get isVirtual => getBool("virtual"); - return elements.join(" | "); - } + bool get isTemplate => getBool("is_template"); - // Return a path to the image for this Part - String get image { - // Use thumbnail as a backup - String img = _image.isNotEmpty ? _image : _thumbnail; + bool get isTrackable => getBool("trackable"); - return img.isNotEmpty ? img : InvenTreeAPI.staticImage; - } + // Get the IPN (internal part number) for the Part instance + String get IPN => getString("IPN"); - // Return a path to the thumbnail for this part - String get thumbnail { - // Use image as a backup - String img = _thumbnail.isNotEmpty ? _thumbnail : _image; + // Get the revision string for the Part instance + String get revision => getString("revision"); - return img.isNotEmpty ? img : InvenTreeAPI.staticThumb; - } + // Get the category ID for the Part instance (or "null" if does not exist) + int get categoryId => getInt("category"); - Future uploadImage(File image) async { - // Upload file against this part - final APIResponse response = await InvenTreeAPI().uploadFile( - url, - image, - method: "PATCH", - name: "image", - ); + // Get the category name for the Part instance + String get categoryName { + // Inavlid category ID + if (categoryId <= 0) return ""; - return response.successful(); - } + if (!jsondata.containsKey("category_detail")) return ""; - // Return the "starred" status of this part - bool get starred => getBool("starred"); + return (jsondata["category_detail"]?["name"] ?? "") as String; + } + + // Get the category description for the Part instance + String get categoryDescription { + // Invalid category ID + if (categoryId <= 0) return ""; + + if (!jsondata.containsKey("category_detail")) return ""; + + return (jsondata["category_detail"]?["description"] ?? "") as String; + } + + // Get the image URL for the Part instance + String get _image => getString("image"); + + // Get the thumbnail URL for the Part instance + String get _thumbnail => getString("thumbnail"); + + // Return the fully-qualified name for the Part instance + String get fullname { + String fn = getString("full_name"); + + if (fn.isNotEmpty) return fn; + + List elements = []; + + if (IPN.isNotEmpty) elements.add(IPN); + + elements.add(name); + + if (revision.isNotEmpty) elements.add(revision); + + return elements.join(" | "); + } + + // Return a path to the image for this Part + String get image { + // Use thumbnail as a backup + String img = _image.isNotEmpty ? _image : _thumbnail; + + return img.isNotEmpty ? img : InvenTreeAPI.staticImage; + } + + // Return a path to the thumbnail for this part + String get thumbnail { + // Use image as a backup + String img = _thumbnail.isNotEmpty ? _thumbnail : _image; + + return img.isNotEmpty ? img : InvenTreeAPI.staticThumb; + } + + Future uploadImage(File image) async { + // Upload file against this part + final APIResponse response = await InvenTreeAPI().uploadFile( + url, + image, + method: "PATCH", + name: "image", + ); + + return response.successful(); + } + + // Return the "starred" status of this part + bool get starred => getBool("starred"); @override - InvenTreeModel createFromJson(Map json) => InvenTreePart.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePart.fromJson(json); } - class InvenTreePartPricing extends InvenTreeModel { - InvenTreePartPricing() : super(); - InvenTreePartPricing.fromJson(Map json) : super.fromJson(json); + InvenTreePartPricing.fromJson(Map json) + : super.fromJson(json); @override List get rolesRequired => ["part"]; @override - InvenTreeModel createFromJson(Map json) => InvenTreePartPricing.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePartPricing.fromJson(json); // Price data accessors String get currency => getString("currency", backup: "USD"); @@ -538,8 +524,10 @@ class InvenTreePartPricing extends InvenTreeModel { double? get overrideMin => getDoubleOrNull("override_min"); double? get overrideMax => getDoubleOrNull("override_max"); - String get overrideMinCurrency => getString("override_min_currency", backup: currency); - String get overrideMaxCurrency => getString("override_max_currency", backup: currency); + String get overrideMinCurrency => + getString("override_min_currency", backup: currency); + String get overrideMaxCurrency => + getString("override_max_currency", backup: currency); double? get bomCostMin => getDoubleOrNull("bom_cost_min"); double? get bomCostMax => getDoubleOrNull("bom_cost_max"); @@ -563,15 +551,14 @@ class InvenTreePartPricing extends InvenTreeModel { double? get saleHistoryMax => getDoubleOrNull("sale_history_max"); } - /* * Class representing an attachment file against a Part object */ class InvenTreePartAttachment extends InvenTreeAttachment { - InvenTreePartAttachment() : super(); - InvenTreePartAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreePartAttachment.fromJson(Map json) + : super.fromJson(json); @override String get REFERENCE_FIELD => "part"; @@ -580,9 +567,11 @@ class InvenTreePartAttachment extends InvenTreeAttachment { String get REF_MODEL_TYPE => "part"; @override - String get URL => InvenTreeAPI().supportsModernAttachments ? "attachment/" : "part/attachment/"; + String get URL => InvenTreeAPI().supportsModernAttachments + ? "attachment/" + : "part/attachment/"; @override - InvenTreeModel createFromJson(Map json) => InvenTreePartAttachment.fromJson(json); - + InvenTreeModel createFromJson(Map json) => + InvenTreePartAttachment.fromJson(json); } diff --git a/lib/inventree/project_code.dart b/lib/inventree/project_code.dart index e5d45289..2c3442d5 100644 --- a/lib/inventree/project_code.dart +++ b/lib/inventree/project_code.dart @@ -1,17 +1,17 @@ import "package:inventree/inventree/model.dart"; - /* * Class representing the ProjectCode database model */ class InvenTreeProjectCode extends InvenTreeModel { - InvenTreeProjectCode() : super(); - InvenTreeProjectCode.fromJson(Map json) : super.fromJson(json); + InvenTreeProjectCode.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeProjectCode.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeProjectCode.fromJson(json); @override String get URL => "project-code/"; diff --git a/lib/inventree/purchase_order.dart b/lib/inventree/purchase_order.dart index 1b8bc7c5..278a0e9d 100644 --- a/lib/inventree/purchase_order.dart +++ b/lib/inventree/purchase_order.dart @@ -12,18 +12,18 @@ import "package:inventree/widget/progress.dart"; import "package:inventree/api_form.dart"; import "package:inventree/l10.dart"; - /* * Class representing an individual PurchaseOrder instance */ class InvenTreePurchaseOrder extends InvenTreeOrder { - InvenTreePurchaseOrder() : super(); - InvenTreePurchaseOrder.fromJson(Map json) : super.fromJson(json); + InvenTreePurchaseOrder.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreePurchaseOrder.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePurchaseOrder.fromJson(json); @override String get URL => "order/po/"; @@ -33,9 +33,7 @@ class InvenTreePurchaseOrder extends InvenTreeOrder { return Navigator.push( context, MaterialPageRoute( - builder: (context) => PurchaseOrderDetailWidget(this) - ) - ); + builder: (context) => PurchaseOrderDetailWidget(this))); } static const String MODEL_TYPE = "purchaseorder"; @@ -82,7 +80,6 @@ class InvenTreePurchaseOrder extends InvenTreeOrder { } return fields; - } @override @@ -95,7 +92,6 @@ class InvenTreePurchaseOrder extends InvenTreeOrder { int get supplierId => getInt("supplier"); InvenTreeCompany? get supplier { - dynamic supplier_detail = jsondata["supplier_detail"]; if (supplier_detail == null) { @@ -109,21 +105,21 @@ class InvenTreePurchaseOrder extends InvenTreeOrder { int get destinationId => getInt("destination"); - bool get isOpen => api.PurchaseOrderStatus.isNameIn(status, ["PENDING", "PLACED", "ON_HOLD"]); + bool get isOpen => api.PurchaseOrderStatus.isNameIn( + status, ["PENDING", "PLACED", "ON_HOLD"]); - bool get isPending => api.PurchaseOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]); + bool get isPending => + api.PurchaseOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]); bool get isPlaced => api.PurchaseOrderStatus.isNameIn(status, ["PLACED"]); - bool get isFailed => api.PurchaseOrderStatus.isNameIn(status, ["CANCELLED", "LOST", "RETURNED"]); + bool get isFailed => api.PurchaseOrderStatus.isNameIn( + status, ["CANCELLED", "LOST", "RETURNED"]); Future> getLineItems() async { - - final results = await InvenTreePOLineItem().list( - filters: { - "order": "${pk}", - } - ); + final results = await InvenTreePOLineItem().list(filters: { + "order": "${pk}", + }); List items = []; @@ -161,13 +157,14 @@ class InvenTreePurchaseOrder extends InvenTreeOrder { } class InvenTreePOLineItem extends InvenTreeOrderLine { - InvenTreePOLineItem() : super(); - InvenTreePOLineItem.fromJson(Map json) : super.fromJson(json); + InvenTreePOLineItem.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreePOLineItem.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePOLineItem.fromJson(json); @override String get URL => "order/po-line/"; @@ -216,14 +213,14 @@ class InvenTreePOLineItem extends InvenTreeOrderLine { return received / quantity; } - String get progressString => simpleNumberString(received) + " / " + simpleNumberString(quantity); + String get progressString => + simpleNumberString(received) + " / " + simpleNumberString(quantity); double get outstanding => quantity - received; int get supplierPartId => getInt("part"); InvenTreeSupplierPart? get supplierPart { - dynamic detail = jsondata["supplier_part_detail"]; if (detail == null) { @@ -246,7 +243,7 @@ class InvenTreePOLineItem extends InvenTreeOrderLine { String get SKU => getString("SKU", subKey: "supplier_part_detail"); double get purchasePrice => getDouble("purchase_price"); - + String get purchasePriceCurrency => getString("purchase_price_currency"); int get destinationId => getInt("destination"); @@ -256,7 +253,11 @@ class InvenTreePOLineItem extends InvenTreeOrderLine { Map get destinationDetail => getMap("destination_detail"); // Receive this line item into stock - Future receive(BuildContext context, {int? destination, double? quantity, String? barcode, Function? onSuccess}) async { + Future receive(BuildContext context, + {int? destination, + double? quantity, + String? barcode, + Function? onSuccess}) async { // Infer the destination location from the line item if not provided if (destinationId > 0) { destination = destinationId; @@ -305,32 +306,26 @@ class InvenTreePOLineItem extends InvenTreeOrderLine { InvenTreePurchaseOrder? order = purchaseOrder; if (order != null) { - await launchApiForm( - context, - L10().receiveItem, - order.receive_url, - fields, + await launchApiForm(context, L10().receiveItem, order.receive_url, fields, method: "POST", - icon: TablerIcons.transition_right, - onSuccess: (data) { - if (onSuccess != null) { - onSuccess(); - } - } - ); + icon: TablerIcons.transition_right, onSuccess: (data) { + if (onSuccess != null) { + onSuccess(); + } + }); } } } - class InvenTreePOExtraLineItem extends InvenTreeExtraLineItem { - InvenTreePOExtraLineItem() : super(); - InvenTreePOExtraLineItem.fromJson(Map json) : super.fromJson(json); + InvenTreePOExtraLineItem.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreePOExtraLineItem.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreePOExtraLineItem.fromJson(json); @override String get URL => "order/po-extra-line/"; @@ -340,25 +335,19 @@ class InvenTreePOExtraLineItem extends InvenTreeExtraLineItem { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ExtraLineDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => ExtraLineDetailWidget(this))); } - } - /* * Class representing an attachment file against a PurchaseOrder object */ class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { - InvenTreePurchaseOrderAttachment() : super(); - InvenTreePurchaseOrderAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreePurchaseOrderAttachment.fromJson(Map json) + : super.fromJson(json); @override String get REFERENCE_FIELD => "order"; @@ -367,9 +356,11 @@ class InvenTreePurchaseOrderAttachment extends InvenTreeAttachment { String get REF_MODEL_TYPE => "purchaseorder"; @override - String get URL => InvenTreeAPI().supportsModernAttachments ? "attachment/" : "order/po/attachment/"; + String get URL => InvenTreeAPI().supportsModernAttachments + ? "attachment/" + : "order/po/attachment/"; @override - InvenTreeModel createFromJson(Map json) => InvenTreePurchaseOrderAttachment.fromJson(json); - + InvenTreeModel createFromJson(Map json) => + InvenTreePurchaseOrderAttachment.fromJson(json); } diff --git a/lib/inventree/sales_order.dart b/lib/inventree/sales_order.dart index babf2a25..c085b10c 100644 --- a/lib/inventree/sales_order.dart +++ b/lib/inventree/sales_order.dart @@ -1,5 +1,3 @@ - - import "package:flutter/material.dart"; import "package:inventree/api.dart"; import "package:inventree/helpers.dart"; @@ -11,18 +9,18 @@ import "package:inventree/widget/progress.dart"; import "package:inventree/widget/order/extra_line_detail.dart"; import "package:inventree/widget/order/sales_order_detail.dart"; - /* * Class representing an individual SalesOrder */ class InvenTreeSalesOrder extends InvenTreeOrder { - InvenTreeSalesOrder() : super(); - InvenTreeSalesOrder.fromJson(Map json) : super.fromJson(json); + InvenTreeSalesOrder.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeSalesOrder.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSalesOrder.fromJson(json); @override String get URL => "order/so/"; @@ -36,12 +34,8 @@ class InvenTreeSalesOrder extends InvenTreeOrder { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SalesOrderDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => SalesOrderDetailWidget(this))); } @override @@ -124,28 +118,30 @@ class InvenTreeSalesOrder extends InvenTreeOrder { String get customerReference => getString("customer_reference"); - bool get isOpen => api.SalesOrderStatus.isNameIn(status, ["PENDING", "IN_PROGRESS", "ON_HOLD"]); + bool get isOpen => api.SalesOrderStatus.isNameIn( + status, ["PENDING", "IN_PROGRESS", "ON_HOLD"]); - bool get isPending => api.SalesOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]); + bool get isPending => + api.SalesOrderStatus.isNameIn(status, ["PENDING", "ON_HOLD"]); - bool get isInProgress => api.SalesOrderStatus.isNameIn(status, ["IN_PROGRESS"]); + bool get isInProgress => + api.SalesOrderStatus.isNameIn(status, ["IN_PROGRESS"]); bool get isComplete => api.SalesOrderStatus.isNameIn(status, ["SHIPPED"]); - } - /* * Class representing an individual line item in a SalesOrder */ class InvenTreeSOLineItem extends InvenTreeOrderLine { - InvenTreeSOLineItem() : super(); - InvenTreeSOLineItem.fromJson(Map json) : super.fromJson(json); + InvenTreeSOLineItem.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeSOLineItem.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSOLineItem.fromJson(json); @override String get URL => "order/so-line/"; @@ -172,7 +168,6 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine { } Map> allocateFormFields() { - return { "line_item": { "parent": "items", @@ -188,9 +183,7 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine { "parent": "items", "nested": true, }, - "shipment": { - "filters": {} - } + "shipment": {"filters": {}} }; } @@ -223,7 +216,8 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine { return unallocated; } - String get allocatedString => simpleNumberString(allocated) + " / " + simpleNumberString(quantity); + String get allocatedString => + simpleNumberString(allocated) + " / " + simpleNumberString(quantity); double get shipped => getDouble("shipped"); @@ -239,26 +233,28 @@ class InvenTreeSOLineItem extends InvenTreeOrderLine { return shipped / quantity; } - String get progressString => simpleNumberString(shipped) + " / " + simpleNumberString(quantity); + String get progressString => + simpleNumberString(shipped) + " / " + simpleNumberString(quantity); bool get isComplete => shipped >= quantity; - double get available => getDouble("available_stock") + getDouble("available_variant_stock"); + double get available => + getDouble("available_stock") + getDouble("available_variant_stock"); double get salePrice => getDouble("sale_price"); String get salePriceCurrency => getString("sale_price_currency"); - } - class InvenTreeSOExtraLineItem extends InvenTreeExtraLineItem { InvenTreeSOExtraLineItem() : super(); - InvenTreeSOExtraLineItem.fromJson(Map json) : super.fromJson(json); + InvenTreeSOExtraLineItem.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeSOExtraLineItem.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSOExtraLineItem.fromJson(json); @override String get URL => "order/so-extra-line/"; @@ -268,12 +264,8 @@ class InvenTreeSOExtraLineItem extends InvenTreeExtraLineItem { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ExtraLineDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => ExtraLineDetailWidget(this))); } } @@ -281,13 +273,14 @@ class InvenTreeSOExtraLineItem extends InvenTreeExtraLineItem { * Class representing a sales order shipment */ class InvenTreeSalesOrderShipment extends InvenTreeModel { - InvenTreeSalesOrderShipment() : super(); - InvenTreeSalesOrderShipment.fromJson(Map json) : super.fromJson(json); + InvenTreeSalesOrderShipment.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeSalesOrderShipment.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSalesOrderShipment.fromJson(json); @override String get URL => "/order/so/shipment/"; @@ -318,19 +311,18 @@ class InvenTreeSalesOrderShipment extends InvenTreeModel { bool get shipped => shipment_date != null && shipment_date!.isNotEmpty; } - - /* * Class representing an attachment file against a SalesOrder object */ class InvenTreeSalesOrderAttachment extends InvenTreeAttachment { - InvenTreeSalesOrderAttachment() : super(); - InvenTreeSalesOrderAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreeSalesOrderAttachment.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeSalesOrderAttachment.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeSalesOrderAttachment.fromJson(json); @override String get REFERENCE_FIELD => "order"; @@ -339,6 +331,7 @@ class InvenTreeSalesOrderAttachment extends InvenTreeAttachment { String get REF_MODEL_TYPE => "salesorder"; @override - String get URL => InvenTreeAPI().supportsModernAttachments ? "attachment/" : "order/so/attachment/"; - + String get URL => InvenTreeAPI().supportsModernAttachments + ? "attachment/" + : "order/so/attachment/"; } diff --git a/lib/inventree/sentry.dart b/lib/inventree/sentry.dart index a052ed57..faccae39 100644 --- a/lib/inventree/sentry.dart +++ b/lib/inventree/sentry.dart @@ -11,7 +11,6 @@ import "package:inventree/dsn.dart"; import "package:inventree/preferences.dart"; Future> getDeviceInfo() async { - // Extract device information final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); @@ -31,7 +30,6 @@ Future> getDeviceInfo() async { "identifierForVendor": iosDeviceInfo.identifierForVendor, "isPhysicalDevice": iosDeviceInfo.isPhysicalDevice, }; - } else if (Platform.isAndroid) { final androidDeviceInfo = await deviceInfo.androidInfo; @@ -57,12 +55,10 @@ Future> getDeviceInfo() async { return device_info; } - Map getServerInfo() => { - "version": InvenTreeAPI().serverVersion, - "apiVersion": InvenTreeAPI().apiVersion, -}; - + "version": InvenTreeAPI().serverVersion, + "apiVersion": InvenTreeAPI().apiVersion, + }; Future> getAppInfo() async { // Add app info @@ -76,7 +72,6 @@ Future> getAppInfo() async { }; } - bool isInDebugMode() { bool inDebugMode = false; @@ -85,8 +80,8 @@ bool isInDebugMode() { return inDebugMode; } -Future sentryReportMessage(String message, {Map? context}) async { - +Future sentryReportMessage(String message, + {Map? context}) async { if (SENTRY_DSN_KEY.isEmpty) { return false; } @@ -106,23 +101,21 @@ Future sentryReportMessage(String message, {Map? context}) // We don't care about the server address, only the path and query parameters! // Overwrite the provided URL context["url"] = uri.path + "?" + uri.query; - } catch (error) { // Ignore if any errors are thrown here } - } } print("Sending user message to Sentry: ${message}, ${context}"); if (isInDebugMode()) { - print("----- In dev mode. Not sending message to Sentry.io -----"); return true; } - final upload = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; + final upload = await InvenTreeSettingsManager() + .getValue(INV_REPORT_ERRORS, true) as bool; if (!upload) { print("----- Error reporting disabled -----"); @@ -152,12 +145,12 @@ Future sentryReportMessage(String message, {Map? context}) } } - /* * Report an error message to sentry.io */ -Future sentryReportError(String source, dynamic error, StackTrace? stackTrace, {Map context = const {}}) async { - +Future sentryReportError( + String source, dynamic error, StackTrace? stackTrace, + {Map context = const {}}) async { if (sentryIgnoreError(error)) { // No action on this error return; @@ -170,7 +163,6 @@ Future sentryReportError(String source, dynamic error, StackTrace? stackTr // check if you are running in dev mode using an assertion and omit sending // the report. if (isInDebugMode()) { - print("----- In dev mode. Not sending report to Sentry.io -----"); return; } @@ -179,7 +171,8 @@ Future sentryReportError(String source, dynamic error, StackTrace? stackTr return; } - final upload = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; + final upload = await InvenTreeSettingsManager() + .getValue(INV_REPORT_ERRORS, true) as bool; if (!upload) { print("----- Error reporting disabled -----"); @@ -188,11 +181,12 @@ Future sentryReportError(String source, dynamic error, StackTrace? stackTr // Some errors are outside our control, and we do not want to "pollute" the uploaded data if (source == "FlutterError.onError") { - String errorString = error.toString(); // Missing media file - if (errorString.contains("HttpException") && errorString.contains("404") && errorString.contains("/media/")) { + if (errorString.contains("HttpException") && + errorString.contains("404") && + errorString.contains("/media/")) { return; } @@ -234,17 +228,17 @@ Future sentryReportError(String source, dynamic error, StackTrace? stackTr }); } - /* * Test if a certain error should be ignored by Sentry */ bool sentryIgnoreError(dynamic error) { // Ignore 404 errors for media files if (error is HttpException) { - if (error.uri.toString().contains("/media/") && error.message.contains("404")) { + if (error.uri.toString().contains("/media/") && + error.message.contains("404")) { return true; } } return false; -} \ No newline at end of file +} diff --git a/lib/inventree/status_codes.dart b/lib/inventree/status_codes.dart index 9b75280d..0058fc8a 100644 --- a/lib/inventree/status_codes.dart +++ b/lib/inventree/status_codes.dart @@ -11,12 +11,10 @@ import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/helpers.dart"; - /* * Base class definition for a "status code" definition. */ class InvenTreeStatusCode { - InvenTreeStatusCode(this.URL); final String URL; @@ -34,10 +32,7 @@ class InvenTreeStatusCode { dynamic _entry = data[key]; if (_entry is Map) { - _choices.add({ - "value": _entry["key"], - "display_name": _entry["label"] - }); + _choices.add({"value": _entry["key"], "display_name": _entry["label"]}); } } @@ -46,7 +41,6 @@ class InvenTreeStatusCode { // Load status code information from the server Future load({bool forceReload = false}) async { - // Return internally cached data if (data.isNotEmpty && !forceReload) { return; diff --git a/lib/inventree/stock.dart b/lib/inventree/stock.dart index 96b2a2fa..f31da482 100644 --- a/lib/inventree/stock.dart +++ b/lib/inventree/stock.dart @@ -10,16 +10,14 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/widget/stock/location_display.dart"; import "package:inventree/widget/stock/stock_detail.dart"; - - /* * Class representing a test result for a single stock item */ class InvenTreeStockItemTestResult extends InvenTreeModel { - InvenTreeStockItemTestResult() : super(); - InvenTreeStockItemTestResult.fromJson(Map json) : super.fromJson(json); + InvenTreeStockItemTestResult.fromJson(Map json) + : super.fromJson(json); @override String get URL => "stock/test/"; @@ -37,7 +35,6 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { @override Map> formFields() { - Map> fields = { "stock_item": {"hidden": true}, "test": {}, @@ -68,39 +65,37 @@ class InvenTreeStockItemTestResult extends InvenTreeModel { String get testName => getString("test"); bool get result => getBool("result"); - + String get value => getString("value"); - + String get attachment => getString("attachment"); String get username => getString("username", subKey: "user_detail"); String get date => getString("date"); - + @override InvenTreeStockItemTestResult createFromJson(Map json) { var result = InvenTreeStockItemTestResult.fromJson(json); return result; } - } - class InvenTreeStockItemHistory extends InvenTreeModel { - InvenTreeStockItemHistory() : super(); - InvenTreeStockItemHistory.fromJson(Map json) : super.fromJson(json); + InvenTreeStockItemHistory.fromJson(Map json) + : super.fromJson(json); @override - InvenTreeModel createFromJson(Map json) => InvenTreeStockItemHistory.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeStockItemHistory.fromJson(json); @override String get URL => "stock/track/"; @override Map defaultFilters() { - // By default, order by decreasing date return { "ordering": "-date", @@ -113,7 +108,7 @@ class InvenTreeStockItemHistory extends InvenTreeModel { String get dateString => getDateString("date"); String get label => getString("label"); - + // Return the "deltas" associated with this historical object Map get deltas => getMap("deltas"); @@ -133,7 +128,6 @@ class InvenTreeStockItemHistory extends InvenTreeModel { int? get user => getValue("user") as int?; String get userString { - if (user != null) { return getString("username", subKey: "user_detail"); } else { @@ -142,12 +136,10 @@ class InvenTreeStockItemHistory extends InvenTreeModel { } } - /* * Class representing a StockItem database instance */ class InvenTreeStockItem extends InvenTreeModel { - InvenTreeStockItem() : super(); InvenTreeStockItem.fromJson(Map json) : super.fromJson(json); @@ -162,12 +154,8 @@ class InvenTreeStockItem extends InvenTreeModel { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StockDetailWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => StockDetailWidget(this))); } // Return a set of fields to transfer this stock item via dialog @@ -250,7 +238,6 @@ class InvenTreeStockItem extends InvenTreeModel { @override Map defaultFilters() { - return { "part_detail": "true", "location_detail": "true", @@ -265,7 +252,7 @@ class InvenTreeStockItem extends InvenTreeModel { int get testTemplateCount => testTemplates.length; // Get all the test templates associated with this StockItem - Future getTestTemplates({bool showDialog=false}) async { + Future getTestTemplates({bool showDialog = false}) async { await InvenTreePartTestTemplate().list( filters: { "part": "${partId}", @@ -287,7 +274,6 @@ class InvenTreeStockItem extends InvenTreeModel { int get testResultCount => testResults.length; Future getTestResults() async { - await InvenTreeStockItemTestResult().list( filters: { "stock_item": "${pk}", @@ -313,7 +299,7 @@ class InvenTreeStockItem extends InvenTreeModel { String get batch => getString("batch"); int get partId => getInt("part"); - + double? get purchasePrice { String pp = getString("purchase_price"); @@ -334,7 +320,7 @@ class InvenTreeStockItem extends InvenTreeModel { int get purchaseOrderId => getInt("purchase_order"); int get trackingItemCount => getInt("tracking_items", backup: 0); - + bool get isBuilding => getBool("is_building"); int get salesOrderId => getInt("sales_order"); @@ -362,274 +348,271 @@ class InvenTreeStockItem extends InvenTreeModel { String get stocktakeDateString => getDateString("stocktake_date"); - String get partName { + String get partName { + String nm = ""; - String nm = ""; - - // Use the detailed part information as priority - if (jsondata.containsKey("part_detail")) { - nm = (jsondata["part_detail"]?["full_name"] ?? "") as String; - } - - // Backup if first value fails - if (nm.isEmpty) { - nm = getString("part__name"); - } - - return nm; + // Use the detailed part information as priority + if (jsondata.containsKey("part_detail")) { + nm = (jsondata["part_detail"]?["full_name"] ?? "") as String; } - String get partDescription { - String desc = ""; - - // Use the detailed part description as priority - if (jsondata.containsKey("part_detail")) { - desc = (jsondata["part_detail"]?["description"] ?? "") as String; - } - - if (desc.isEmpty) { - desc = getString("part__description"); - } - - return desc; + // Backup if first value fails + if (nm.isEmpty) { + nm = getString("part__name"); } - String get partImage { - String img = ""; + return nm; + } - if (jsondata.containsKey("part_detail")) { - img = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; - } + String get partDescription { + String desc = ""; - if (img.isEmpty) { - img = getString("part__thumbnail"); - } - - return img; + // Use the detailed part description as priority + if (jsondata.containsKey("part_detail")) { + desc = (jsondata["part_detail"]?["description"] ?? "") as String; } - /* + if (desc.isEmpty) { + desc = getString("part__description"); + } + + return desc; + } + + String get partImage { + String img = ""; + + if (jsondata.containsKey("part_detail")) { + img = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; + } + + if (img.isEmpty) { + img = getString("part__thumbnail"); + } + + return img; + } + + /* * Return the Part thumbnail for this stock item. */ - String get partThumbnail { + String get partThumbnail { + String thumb = ""; - String thumb = ""; + thumb = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; - thumb = (jsondata["part_detail"]?["thumbnail"] ?? "") as String; - - // Use "image" as a backup - if (thumb.isEmpty) { - thumb = (jsondata["part_detail"]?["image"] ?? "") as String; - } - - // Try a different approach - if (thumb.isEmpty) { - thumb = getString("part__thumbnail"); - } - - // Still no thumbnail? Use the "no image" image - if (thumb.isEmpty) thumb = InvenTreeAPI.staticThumb; - - return thumb; + // Use "image" as a backup + if (thumb.isEmpty) { + thumb = (jsondata["part_detail"]?["image"] ?? "") as String; } - int get supplierPartId => getInt("supplier_part"); - - String get supplierImage { - String thumb = ""; - - if (jsondata.containsKey("supplier_part_detail")) { - thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? "") as String; - } else if (jsondata.containsKey("supplier_detail")) { - thumb = (jsondata["supplier_detail"]?["image"] ?? "") as String; - } - - return thumb; + // Try a different approach + if (thumb.isEmpty) { + thumb = getString("part__thumbnail"); } - String get supplierName => getString("supplier_name", subKey: "supplier_detail"); + // Still no thumbnail? Use the "no image" image + if (thumb.isEmpty) thumb = InvenTreeAPI.staticThumb; - String get units => getString("units", subKey: "part_detail"); + return thumb; + } - String get supplierSKU => getString("SKU", subKey: "supplier_part_detail"); + int get supplierPartId => getInt("supplier_part"); - String get serialNumber => getString("serial"); + String get supplierImage { + String thumb = ""; - double get quantity => getDouble("quantity"); + if (jsondata.containsKey("supplier_part_detail")) { + thumb = (jsondata["supplier_part_detail"]?["supplier_detail"]?["image"] ?? + "") as String; + } else if (jsondata.containsKey("supplier_detail")) { + thumb = (jsondata["supplier_detail"]?["image"] ?? "") as String; + } - String quantityString({bool includeUnits = true}){ + return thumb; + } - String q = ""; + String get supplierName => + getString("supplier_name", subKey: "supplier_detail"); - if (allocated > 0) { - q += simpleNumberString(available); - q += " / "; - } + String get units => getString("units", subKey: "part_detail"); - q += simpleNumberString(quantity); + String get supplierSKU => getString("SKU", subKey: "supplier_part_detail"); - if (includeUnits && units.isNotEmpty) { + String get serialNumber => getString("serial"); + + double get quantity => getDouble("quantity"); + + String quantityString({bool includeUnits = true}) { + String q = ""; + + if (allocated > 0) { + q += simpleNumberString(available); + q += " / "; + } + + q += simpleNumberString(quantity); + + if (includeUnits && units.isNotEmpty) { + q += " ${units}"; + } + + return q; + } + + double get allocated => getDouble("allocated"); + + double get available => quantity - allocated; + + int get locationId => getInt("location"); + + bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1; + + String serialOrQuantityDisplay() { + if (isSerialized()) { + return "SN ${serialNumber}"; + } else if (allocated > 0) { + return "${available} / ${quantity}"; + } else { + return simpleNumberString(quantity); + } + } + + String get locationName { + if (locationId == -1 || !jsondata.containsKey("location_detail")) + return "Unknown Location"; + + String loc = getString("name", subKey: "location_detail"); + + // Old-style name + if (loc.isEmpty) { + loc = getString("location__name"); + } + + return loc; + } + + String get locationPathString { + if (locationId == -1 || !jsondata.containsKey("location_detail")) + return L10().locationNotSet; + + String _loc = getString("pathstring", subKey: "location_detail"); + if (_loc.isNotEmpty) { + return _loc; + } else { + return locationName; + } + } + + String get displayQuantity { + // Display either quantity or serial number! + + if (serialNumber.isNotEmpty) { + return "SN: $serialNumber"; + } else { + String q = simpleNumberString(quantity); + + if (units.isNotEmpty) { q += " ${units}"; } return q; } + } - double get allocated => getDouble("allocated"); + @override + InvenTreeModel createFromJson(Map json) => + InvenTreeStockItem.fromJson(json); - double get available => quantity - allocated; - - int get locationId => getInt("location"); - - bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1; - - String serialOrQuantityDisplay() { - if (isSerialized()) { - return "SN ${serialNumber}"; - } else if (allocated > 0) { - return "${available} / ${quantity}"; - } else { - return simpleNumberString(quantity); - } - } - - String get locationName { - - if (locationId == -1 || !jsondata.containsKey("location_detail")) return "Unknown Location"; - - String loc = getString("name", subKey: "location_detail"); - - // Old-style name - if (loc.isEmpty) { - loc = getString("location__name"); - } - - return loc; - } - - String get locationPathString { - - if (locationId == -1 || !jsondata.containsKey("location_detail")) return L10().locationNotSet; - - String _loc = getString("pathstring", subKey: "location_detail"); - if (_loc.isNotEmpty) { - return _loc; - } else { - return locationName; - } - } - - String get displayQuantity { - // Display either quantity or serial number! - - if (serialNumber.isNotEmpty) { - return "SN: $serialNumber"; - } else { - String q = simpleNumberString(quantity); - - if (units.isNotEmpty) { - q += " ${units}"; - } - - return q; - } - } - - @override - InvenTreeModel createFromJson(Map json) => InvenTreeStockItem.fromJson(json); - - /* + /* * Perform stocktake action: * * - Add * - Remove * - Count */ - Future adjustStock(String endpoint, double q, {String? notes, int? location}) async { - - // Serialized stock cannot be adjusted (unless it is a "transfer") - if (isSerialized() && location == null) { - return false; - } - - // Cannot handle negative stock - if (q < 0) { - return false; - } - - Map data = {}; - - data = { - "items": [ - { - "pk": "${pk}", - "quantity": "${quantity}", - } - ], - "notes": notes ?? "", - }; - - if (location != null) { - data["location"] = location; - } - - var response = await api.post( - endpoint, - body: data, - ); - - return response.isValid() && (response.statusCode == 200 || response.statusCode == 201); + Future adjustStock(String endpoint, double q, + {String? notes, int? location}) async { + // Serialized stock cannot be adjusted (unless it is a "transfer") + if (isSerialized() && location == null) { + return false; } - Future countStock(double q, {String? notes}) async { - - final bool result = await adjustStock("/stock/count/", q, notes: notes); - - return result; + // Cannot handle negative stock + if (q < 0) { + return false; } - Future addStock(double q, {String? notes}) async { + Map data = {}; - final bool result = await adjustStock("/stock/add/", q, notes: notes); + data = { + "items": [ + { + "pk": "${pk}", + "quantity": "${quantity}", + } + ], + "notes": notes ?? "", + }; - return result; + if (location != null) { + data["location"] = location; } - Future removeStock(double q, {String? notes}) async { + var response = await api.post( + endpoint, + body: data, + ); - final bool result = await adjustStock("/stock/remove/", q, notes: notes); - - return result; - } - - Future transferStock(int location, {double? quantity, String? notes}) async { - - double q = this.quantity; - - if (quantity != null) { - q = quantity; - } - - final bool result = await adjustStock( - "/stock/transfer/", - q, - notes: notes, - location: location, - ); - - return result; - } + return response.isValid() && + (response.statusCode == 200 || response.statusCode == 201); } + Future countStock(double q, {String? notes}) async { + 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); + + return result; + } + + Future removeStock(double q, {String? notes}) async { + final bool result = await adjustStock("/stock/remove/", q, notes: notes); + + return result; + } + + Future transferStock(int location, + {double? quantity, String? notes}) async { + double q = this.quantity; + + if (quantity != null) { + q = quantity; + } + + final bool result = await adjustStock( + "/stock/transfer/", + q, + notes: notes, + location: location, + ); + + return result; + } +} /* * Class representing an attachment file against a StockItem object */ class InvenTreeStockItemAttachment extends InvenTreeAttachment { - InvenTreeStockItemAttachment() : super(); - InvenTreeStockItemAttachment.fromJson(Map json) : super.fromJson(json); + InvenTreeStockItemAttachment.fromJson(Map json) + : super.fromJson(json); @override String get REFERENCE_FIELD => "stock_item"; @@ -638,18 +621,20 @@ class InvenTreeStockItemAttachment extends InvenTreeAttachment { String get REF_MODEL_TYPE => "stockitem"; @override - String get URL => InvenTreeAPI().supportsModernAttachments ? "attachment/" : "stock/attachment/"; + String get URL => InvenTreeAPI().supportsModernAttachments + ? "attachment/" + : "stock/attachment/"; @override - InvenTreeModel createFromJson(Map json) => InvenTreeStockItemAttachment.fromJson(json); + InvenTreeModel createFromJson(Map json) => + InvenTreeStockItemAttachment.fromJson(json); } - class InvenTreeStockLocation extends InvenTreeModel { - InvenTreeStockLocation() : super(); - InvenTreeStockLocation.fromJson(Map json) : super.fromJson(json); + InvenTreeStockLocation.fromJson(Map json) + : super.fromJson(json); @override String get URL => "stock/location/"; @@ -663,12 +648,8 @@ class InvenTreeStockLocation extends InvenTreeModel { @override Future goToDetailPage(BuildContext context) async { - return Navigator.push( - context, - MaterialPageRoute( - builder: (context) => LocationDisplayWidget(this) - ) - ); + return Navigator.push(context, + MaterialPageRoute(builder: (context) => LocationDisplayWidget(this))); } @override @@ -684,7 +665,6 @@ class InvenTreeStockLocation extends InvenTreeModel { } String get parentPathString { - List psplit = pathstring.split("/"); if (psplit.isNotEmpty) { @@ -703,6 +683,6 @@ class InvenTreeStockLocation extends InvenTreeModel { int get itemcount => (jsondata["items"] ?? 0) as int; @override - InvenTreeModel createFromJson(Map json) => InvenTreeStockLocation.fromJson(json); - + InvenTreeModel createFromJson(Map json) => + InvenTreeStockLocation.fromJson(json); } diff --git a/lib/l10.dart b/lib/l10.dart index 76a7558c..4b58a93d 100644 --- a/lib/l10.dart +++ b/lib/l10.dart @@ -7,8 +7,7 @@ import "package:flutter/material.dart"; import "package:inventree/helpers.dart"; // Shortcut function to reduce boilerplate! -I18N L10() -{ +I18N L10() { // Testing mode - ignore context if (!hasContext()) { return I18NEn(); @@ -26,4 +25,4 @@ I18N L10() // Fallback for "null" context return I18NEn(); -} \ No newline at end of file +} diff --git a/lib/labels.dart b/lib/labels.dart index ca145e91..2f8ede91 100644 --- a/lib/labels.dart +++ b/lib/labels.dart @@ -18,7 +18,6 @@ Future selectAndPrintLabel( String labelType, String labelQuery, ) async { - if (!InvenTreeAPI().isConnected()) { return; } @@ -63,7 +62,8 @@ Future selectAndPrintLabel( }); } - String selectedPlugin = await InvenTreeAPI().getUserSetting("LABEL_DEFAULT_PRINTER"); + String selectedPlugin = + await InvenTreeAPI().getUserSetting("LABEL_DEFAULT_PRINTER"); if (selectedPlugin.isNotEmpty) { initial_plugin = selectedPlugin; @@ -88,98 +88,84 @@ Future selectAndPrintLabel( } }; - launchApiForm( - context, - L10().printLabel, - "", - fields, - icon: TablerIcons.printer, - validate: (Map data) { - final template = data["label"]; - final plugin = data["plugin"]; + launchApiForm(context, L10().printLabel, "", fields, + icon: TablerIcons.printer, validate: (Map data) { + final template = data["label"]; + final plugin = data["plugin"]; - if (template == null) { - showSnackIcon( - L10().labelSelectTemplate, - success: false, - ); - return false; - } + if (template == null) { + showSnackIcon( + L10().labelSelectTemplate, + success: false, + ); + return false; + } - if (plugin == null) { - showSnackIcon( - L10().labelSelectPrinter, - success: false, - ); - return false; - } + if (plugin == null) { + showSnackIcon( + L10().labelSelectPrinter, + success: false, + ); + return false; + } - return true; - }, - onSuccess: (Map data) async { - int labelId = (data["label"] ?? -1) as int; - var pluginKey = data["plugin"]; + return true; + }, onSuccess: (Map data) async { + int labelId = (data["label"] ?? -1) as int; + var pluginKey = data["plugin"]; - bool result = false; + bool result = false; - if (labelId != -1 && pluginKey != null) { + if (labelId != -1 && pluginKey != null) { + showLoadingOverlay(); - showLoadingOverlay(); + if (InvenTreeAPI().supportsModernLabelPrinting) { + // Modern label printing API uses a POST request to a single API endpoint. + await InvenTreeAPI().post("/label/print/", body: { + "plugin": pluginKey, + "template": labelId, + "items": [instanceId] + }).then((APIResponse response) { + if (response.isValid() && + response.statusCode >= 200 && + response.statusCode <= 201) { + var data = response.asMap(); - if (InvenTreeAPI().supportsModernLabelPrinting) { + if (data.containsKey("output")) { + String? label_file = (data["output"]) as String?; - // Modern label printing API uses a POST request to a single API endpoint. - await InvenTreeAPI().post( - "/label/print/", - body: { - "plugin": pluginKey, - "template": labelId, - "items": [instanceId] - } - ).then((APIResponse response) { - - if (response.isValid() && response.statusCode >= 200 && - response.statusCode <= 201) { - var data = response.asMap(); - - if (data.containsKey("output")) { - String? label_file = (data["output"]) as String?; - - if (label_file != null && label_file.isNotEmpty) { - // Attempt to open generated file - InvenTreeAPI().downloadFile(label_file); - } - - result = true; - } - } - }); - - } else { - // Legacy label printing API - // Uses a GET request to a specially formed URL which depends on the parameters - String url = "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}"; - await InvenTreeAPI().get(url).then((APIResponse response) { - if (response.isValid() && response.statusCode == 200) { - var data = response.asMap(); - if (data.containsKey("file")) { - var label_file = (data["file"] ?? "") as String; - - // Attempt to open remote file + if (label_file != null && label_file.isNotEmpty) { + // Attempt to open generated file InvenTreeAPI().downloadFile(label_file); - result = true; } + + result = true; } - }); + } + }); + } else { + // Legacy label printing API + // Uses a GET request to a specially formed URL which depends on the parameters + String url = + "/label/${labelType}/${labelId}/print/?${labelQuery}&plugin=${pluginKey}"; + await InvenTreeAPI().get(url).then((APIResponse response) { + if (response.isValid() && response.statusCode == 200) { + var data = response.asMap(); + if (data.containsKey("file")) { + var label_file = (data["file"] ?? "") as String; + + // Attempt to open remote file + InvenTreeAPI().downloadFile(label_file); + result = true; + } + } + }); } hideLoadingOverlay(); if (result) { - showSnackIcon( - L10().printLabelSuccess, - success: true - ); + showSnackIcon(L10().printLabelSuccess, success: true); } else { showSnackIcon( L10().printLabelFailure, @@ -190,7 +176,6 @@ Future selectAndPrintLabel( }); } - /* * Discover which label templates are available for a given item */ @@ -198,8 +183,8 @@ Future>> getLabelTemplates( String labelType, Map data, ) async { - - if (!InvenTreeAPI().isConnected() || !InvenTreeAPI().supportsMixin("labels")) { + if (!InvenTreeAPI().isConnected() || + !InvenTreeAPI().supportsMixin("labels")) { return []; } @@ -217,10 +202,12 @@ Future>> getLabelTemplates( List> labels = []; - await InvenTreeAPI().get( + await InvenTreeAPI() + .get( url, params: data, - ).then((APIResponse response) { + ) + .then((APIResponse response) { if (response.isValid() && response.statusCode == 200) { for (var label in response.resultsList()) { if (label is Map) { @@ -231,4 +218,4 @@ Future>> getLabelTemplates( }); return labels; -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index e1ea41a2..ee532142 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,15 +18,12 @@ import "package:inventree/l10n/collected/app_localizations.dart"; import "package:inventree/settings/release.dart"; import "package:inventree/widget/home.dart"; - Future main() async { - WidgetsFlutterBinding.ensureInitialized(); final savedThemeMode = await AdaptiveTheme.getThemeMode(); await runZonedGuarded>(() async { - PackageInfo info = await PackageInfo.fromPlatform(); String pkg = info.packageName; String version = info.version; @@ -46,20 +43,18 @@ Future main() async { // Pass any flutter errors off to the Sentry reporting context! FlutterError.onError = (FlutterErrorDetails details) async { - // Ensure that the error gets reported to sentry! await sentryReportError( - "FlutterError.onError", - details.exception, details.stack, - context: { - "context": details.context.toString(), - "summary": details.summary.toString(), - "library": details.library ?? "null", - } - ); + "FlutterError.onError", details.exception, details.stack, + context: { + "context": details.context.toString(), + "summary": details.summary.toString(), + "library": details.library ?? "null", + }); }; - final int orientation = await InvenTreeSettingsManager().getValue(INV_SCREEN_ORIENTATION, SCREEN_ORIENTATION_SYSTEM) as int; + final int orientation = await InvenTreeSettingsManager() + .getValue(INV_SCREEN_ORIENTATION, SCREEN_ORIENTATION_SYSTEM) as int; List orientations = []; @@ -78,15 +73,11 @@ Future main() async { } SystemChrome.setPreferredOrientations(orientations).then((_) { - runApp( - InvenTreeApp(savedThemeMode) - ); + runApp(InvenTreeApp(savedThemeMode)); }); - }, (Object error, StackTrace stackTrace) async { sentryReportError("main.runZonedGuarded", error, stackTrace); }); - } class InvenTreeApp extends StatefulWidget { @@ -99,13 +90,11 @@ class InvenTreeApp extends StatefulWidget { @override InvenTreeAppState createState() => InvenTreeAppState(savedThemeMode); - static InvenTreeAppState? of(BuildContext context) => context.findAncestorStateOfType(); - + static InvenTreeAppState? of(BuildContext context) => + context.findAncestorStateOfType(); } - class InvenTreeAppState extends State { - InvenTreeAppState(this.savedThemeMode) : super(); // Custom _locale (default = null; use system default) @@ -123,16 +112,16 @@ class InvenTreeAppState extends State { // Run app init routines in the background Future runInitTasks() async { - // Set the app locale (language) Locale? locale = await InvenTreeSettingsManager().getSelectedLocale(); setLocale(locale); // Display release notes if this is a new version - final String version = await InvenTreeSettingsManager().getValue("recentVersion", "") as String; + final String version = await InvenTreeSettingsManager() + .getValue("recentVersion", "") as String; final PackageInfo info = await PackageInfo.fromPlatform(); - + if (version != info.version) { // Save latest version to the settings database await InvenTreeSettingsManager().setValue("recentVersion", info.version); @@ -142,8 +131,7 @@ class InvenTreeAppState extends State { // Show the release notes OneContext().push( - MaterialPageRoute(builder: (context) => ReleaseNotesWidget(notes)) - ); + MaterialPageRoute(builder: (context) => ReleaseNotesWidget(notes))); } } @@ -158,37 +146,35 @@ class InvenTreeAppState extends State { @override Widget build(BuildContext context) { - return AdaptiveTheme( - light: ThemeData( - brightness: Brightness.light, - colorSchemeSeed: Colors.lightBlueAccent, - useMaterial3: true, - ), - dark: ThemeData( - brightness: Brightness.dark, - colorSchemeSeed: Colors.blue, - useMaterial3: true, - ), - initial: savedThemeMode ?? AdaptiveThemeMode.light, - builder: (light, dark) => MaterialApp( - theme: light, - darkTheme: dark, - debugShowCheckedModeBanner: false, - builder: OneContext().builder, - navigatorKey: OneContext().key, - onGenerateTitle: (BuildContext context) => "InvenTree", - home: InvenTreeHomePage(), - localizationsDelegates: [ - I18N.delegate, - LocaleNamesLocalizationsDelegate(), - GlobalMaterialLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - supportedLocales: supported_locales, - locale: _locale, - ) - ); + light: ThemeData( + brightness: Brightness.light, + colorSchemeSeed: Colors.lightBlueAccent, + useMaterial3: true, + ), + dark: ThemeData( + brightness: Brightness.dark, + colorSchemeSeed: Colors.blue, + useMaterial3: true, + ), + initial: savedThemeMode ?? AdaptiveThemeMode.light, + builder: (light, dark) => MaterialApp( + theme: light, + darkTheme: dark, + debugShowCheckedModeBanner: false, + builder: OneContext().builder, + navigatorKey: OneContext().key, + onGenerateTitle: (BuildContext context) => "InvenTree", + home: InvenTreeHomePage(), + localizationsDelegates: [ + I18N.delegate, + LocaleNamesLocalizationsDelegate(), + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + supportedLocales: supported_locales, + locale: _locale, + )); } } diff --git a/lib/preferences.dart b/lib/preferences.dart index 0959ff5b..e10c3d93 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -6,7 +6,6 @@ import "package:path_provider/path_provider.dart"; 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"; @@ -62,7 +61,6 @@ const int BARCODE_CONTROLLER_WEDGE = 1; * Class for storing InvenTree preferences in a NoSql DB */ class InvenTreePreferencesDB { - InvenTreePreferencesDB._(); static final InvenTreePreferencesDB _singleton = InvenTreePreferencesDB._(); @@ -74,7 +72,6 @@ class InvenTreePreferencesDB { bool isOpen = false; Future get database async { - if (!isOpen) { // Calling _openDatabase will also complete the completer with database instance _openDatabase(); @@ -101,13 +98,11 @@ class InvenTreePreferencesDB { } } - /* * InvenTree setings manager class. * Provides functions for loading and saving settings, with provision for default values */ class InvenTreeSettingsManager { - factory InvenTreeSettingsManager() { return _manager; } @@ -144,7 +139,6 @@ class InvenTreeSettingsManager { } Future getValue(String key, dynamic backup) async { - dynamic value = await store.record(key).get(await _db); // Retrieve value @@ -174,7 +168,6 @@ class InvenTreeSettingsManager { // Store a key:value pair in the database Future setValue(String key, dynamic value) async { - // Encode null values as strings value ??= "__null__"; @@ -182,5 +175,6 @@ class InvenTreeSettingsManager { } // Ensure we only ever create a single instance of this class - static final InvenTreeSettingsManager _manager = InvenTreeSettingsManager._internal(); + static final InvenTreeSettingsManager _manager = + InvenTreeSettingsManager._internal(); } diff --git a/lib/settings/about.dart b/lib/settings/about.dart index 27a5cd10..8fceccbb 100644 --- a/lib/settings/about.dart +++ b/lib/settings/about.dart @@ -13,34 +13,26 @@ import "package:url_launcher/url_launcher.dart"; const String DOCS_URL = "https://docs.inventree.org/app"; class InvenTreeAboutWidget extends StatelessWidget { - const InvenTreeAboutWidget(this.info) : super(); final PackageInfo info; - Future _releaseNotes(BuildContext context) async { - + Future _releaseNotes(BuildContext context) async { // Load release notes from external file String notes = await rootBundle.loadString("assets/release_notes.md"); - Navigator.push( - context, - MaterialPageRoute(builder: (context) => ReleaseNotesWidget(notes)) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => ReleaseNotesWidget(notes))); } - Future _credits(BuildContext context) async { - + Future _credits(BuildContext context) async { String notes = await rootBundle.loadString("assets/credits.md"); Navigator.push( - context, - MaterialPageRoute(builder: (context) => CreditsWidget(notes)) - ); + context, MaterialPageRoute(builder: (context) => CreditsWidget(notes))); } - Future _openDocs() async { - + Future _openDocs() async { var docsUrl = Uri.parse(DOCS_URL); if (await canLaunchUrl(docsUrl)) { @@ -48,8 +40,7 @@ class InvenTreeAboutWidget extends StatelessWidget { } } - Future _reportBug(BuildContext context) async { - + Future _reportBug(BuildContext context) async { var url = Uri( scheme: "https", host: "github.com", @@ -60,11 +51,9 @@ class InvenTreeAboutWidget extends StatelessWidget { } } - Future _translate() async { - var url = Uri( - scheme: "https", - host: "crowdin.com", - path: "/project/inventree"); + Future _translate() async { + var url = + Uri(scheme: "https", host: "crowdin.com", path: "/project/inventree"); if (await canLaunchUrl(url)) { await launchUrl(url); @@ -73,165 +62,137 @@ class InvenTreeAboutWidget extends StatelessWidget { @override Widget build(BuildContext context) { - List tiles = []; - tiles.add( - ListTile( - title: Text( - L10().serverDetails, - style: TextStyle(fontWeight: FontWeight.bold), - ), - ) - ); + tiles.add(ListTile( + title: Text( + L10().serverDetails, + style: TextStyle(fontWeight: FontWeight.bold), + ), + )); if (InvenTreeAPI().isConnected()) { - tiles.add( - ListTile( - title: Text(L10().address), - subtitle: Text(InvenTreeAPI().baseUrl.isNotEmpty ? InvenTreeAPI().baseUrl : L10().notConnected), - leading: Icon(TablerIcons.globe), - trailing: InvenTreeAPI().isConnected() ? Icon(TablerIcons.circle_check, color: COLOR_SUCCESS) : Icon(TablerIcons.circle_x, color: COLOR_DANGER), - ) - ); + tiles.add(ListTile( + title: Text(L10().address), + subtitle: Text(InvenTreeAPI().baseUrl.isNotEmpty + ? InvenTreeAPI().baseUrl + : L10().notConnected), + leading: Icon(TablerIcons.globe), + trailing: InvenTreeAPI().isConnected() + ? Icon(TablerIcons.circle_check, color: COLOR_SUCCESS) + : Icon(TablerIcons.circle_x, color: COLOR_DANGER), + )); - tiles.add( - ListTile( - title: Text(L10().username), - subtitle: Text(InvenTreeAPI().username), - leading: InvenTreeAPI().username.isNotEmpty ? Icon(TablerIcons.user) : Icon(TablerIcons.user_cancel, color: COLOR_DANGER), - ) - ); + tiles.add(ListTile( + title: Text(L10().username), + subtitle: Text(InvenTreeAPI().username), + leading: InvenTreeAPI().username.isNotEmpty + ? Icon(TablerIcons.user) + : Icon(TablerIcons.user_cancel, color: COLOR_DANGER), + )); - tiles.add( - ListTile( - title: Text(L10().version), - subtitle: Text(InvenTreeAPI().serverVersion.isNotEmpty ? InvenTreeAPI().serverVersion : L10().notConnected), - leading: Icon(TablerIcons.info_circle), - ) - ); + tiles.add(ListTile( + title: Text(L10().version), + subtitle: Text(InvenTreeAPI().serverVersion.isNotEmpty + ? InvenTreeAPI().serverVersion + : L10().notConnected), + leading: Icon(TablerIcons.info_circle), + )); - tiles.add( - ListTile( - title: Text(L10().serverInstance), - subtitle: Text(InvenTreeAPI().serverInstance.isNotEmpty ? InvenTreeAPI().serverInstance : L10().notConnected), - leading: Icon(TablerIcons.server), - ) - ); + tiles.add(ListTile( + title: Text(L10().serverInstance), + subtitle: Text(InvenTreeAPI().serverInstance.isNotEmpty + ? InvenTreeAPI().serverInstance + : L10().notConnected), + leading: Icon(TablerIcons.server), + )); // Display extra tile if the server supports plugins - tiles.add( - ListTile( - title: Text(L10().pluginSupport), - subtitle: Text(L10().pluginSupportDetail), - leading: Icon(TablerIcons.plug), - ) - ); - + tiles.add(ListTile( + title: Text(L10().pluginSupport), + subtitle: Text(L10().pluginSupportDetail), + leading: Icon(TablerIcons.plug), + )); } else { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().notConnected), subtitle: Text( L10().serverNotConnected, style: TextStyle(fontStyle: FontStyle.italic), ), - leading: Icon(TablerIcons.exclamation_circle) - ) - ); + leading: Icon(TablerIcons.exclamation_circle))); } - tiles.add( - ListTile( - title: Text( - L10().appDetails, - style: TextStyle(fontWeight: FontWeight.bold), - ), - ) - ); + tiles.add(ListTile( + title: Text( + L10().appDetails, + style: TextStyle(fontWeight: FontWeight.bold), + ), + )); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().packageName), subtitle: Text("${info.packageName}"), - leading: Icon(TablerIcons.box) - ) - ); + leading: Icon(TablerIcons.box))); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().version), subtitle: Text("${info.version} - Build ${info.buildNumber}"), - leading: Icon(TablerIcons.info_circle) - ) - ); + leading: Icon(TablerIcons.info_circle))); - tiles.add( - ListTile( - title: Text(L10().releaseNotes), - subtitle: Text(L10().appReleaseNotes), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - onTap: () { - _releaseNotes(context); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().releaseNotes), + subtitle: Text(L10().appReleaseNotes), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + onTap: () { + _releaseNotes(context); + }, + )); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().credits), subtitle: Text(L10().appCredits), leading: Icon(TablerIcons.balloon, color: COLOR_ACTION), onTap: () { _credits(context); - } - ) - ); + })); - tiles.add( - ListTile( - title: Text(L10().documentation), - subtitle: Text(DOCS_URL), - leading: Icon(TablerIcons.book, color: COLOR_ACTION), - onTap: () { - _openDocs(); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().documentation), + subtitle: Text(DOCS_URL), + leading: Icon(TablerIcons.book, color: COLOR_ACTION), + onTap: () { + _openDocs(); + }, + )); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().translate), subtitle: Text(L10().translateHelp), leading: Icon(TablerIcons.language, color: COLOR_ACTION), onTap: () { _translate(); - } - ) - ); + })); - tiles.add( - ListTile( - title: Text(L10().reportBug), - subtitle: Text(L10().reportBugDescription), - leading: Icon(TablerIcons.bug, color: COLOR_ACTION), - onTap: () { + tiles.add(ListTile( + title: Text(L10().reportBug), + subtitle: Text(L10().reportBugDescription), + leading: Icon(TablerIcons.bug, color: COLOR_ACTION), + onTap: () { _reportBug(context); - }, - ) - ); + }, + )); return Scaffold( - appBar: AppBar( - title: Text(L10().appAbout), - backgroundColor: COLOR_APP_BAR, - ), - body: ListView( - children: ListTile.divideTiles( - context: context, - tiles: tiles, - ).toList(), - ) - ); + appBar: AppBar( + title: Text(L10().appAbout), + backgroundColor: COLOR_APP_BAR, + ), + body: ListView( + children: ListTile.divideTiles( + context: context, + tiles: tiles, + ).toList(), + )); } -} \ No newline at end of file +} diff --git a/lib/settings/app_settings.dart b/lib/settings/app_settings.dart index 8eba55bd..2f17f127 100644 --- a/lib/settings/app_settings.dart +++ b/lib/settings/app_settings.dart @@ -16,17 +16,16 @@ import "package:inventree/preferences.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/progress.dart"; - class InvenTreeAppSettingsWidget extends StatefulWidget { @override _InvenTreeAppSettingsState createState() => _InvenTreeAppSettingsState(); } class _InvenTreeAppSettingsState extends State { - _InvenTreeAppSettingsState(); - final GlobalKey<_InvenTreeAppSettingsState> _settingsKey = GlobalKey<_InvenTreeAppSettingsState>(); + final GlobalKey<_InvenTreeAppSettingsState> _settingsKey = + GlobalKey<_InvenTreeAppSettingsState>(); // Sound settings bool barcodeSounds = true; @@ -48,16 +47,21 @@ class _InvenTreeAppSettingsState extends State { loadSettings(OneContext().context!); } - Future loadSettings(BuildContext context) async { - + Future loadSettings(BuildContext context) async { showLoadingOverlay(); - barcodeSounds = await InvenTreeSettingsManager().getValue(INV_SOUNDS_BARCODE, true) as bool; - serverSounds = await InvenTreeSettingsManager().getValue(INV_SOUNDS_SERVER, true) as bool; - reportErrors = await InvenTreeSettingsManager().getValue(INV_REPORT_ERRORS, true) as bool; - strictHttps = await InvenTreeSettingsManager().getValue(INV_STRICT_HTTPS, false) as bool; - screenOrientation = await InvenTreeSettingsManager().getValue(INV_SCREEN_ORIENTATION, SCREEN_ORIENTATION_SYSTEM) as int; - enableLabelPrinting = await InvenTreeSettingsManager().getValue(INV_ENABLE_LABEL_PRINTING, true) as bool; + barcodeSounds = await InvenTreeSettingsManager() + .getValue(INV_SOUNDS_BARCODE, true) as bool; + serverSounds = await InvenTreeSettingsManager() + .getValue(INV_SOUNDS_SERVER, true) as bool; + reportErrors = await InvenTreeSettingsManager() + .getValue(INV_REPORT_ERRORS, true) as bool; + strictHttps = await InvenTreeSettingsManager() + .getValue(INV_STRICT_HTTPS, false) as bool; + screenOrientation = await InvenTreeSettingsManager() + .getValue(INV_SCREEN_ORIENTATION, SCREEN_ORIENTATION_SYSTEM) as int; + enableLabelPrinting = await InvenTreeSettingsManager() + .getValue(INV_ENABLE_LABEL_PRINTING, true) as bool; darkMode = AdaptiveTheme.of(context).mode.isDark; @@ -71,7 +75,6 @@ class _InvenTreeAppSettingsState extends State { } Future _selectLocale(BuildContext context) async { - List> options = [ { "display_name": L10().languageDefault, @@ -96,46 +99,39 @@ class _InvenTreeAppSettingsState extends State { } }; - launchApiForm( - context, - L10().languageSelect, - "", - fields, - icon: TablerIcons.circle_check, - onSuccess: (Map data) async { + launchApiForm(context, L10().languageSelect, "", fields, + icon: TablerIcons.circle_check, + onSuccess: (Map data) async { + String locale_name = (data["locale"] ?? "") as String; + Locale? selected_locale; - String locale_name = (data["locale"] ?? "") as String; - Locale? selected_locale; - - for (var locale in supported_locales) { - if (locale.toString() == locale_name) { - selected_locale = locale; - } + for (var locale in supported_locales) { + if (locale.toString() == locale_name) { + selected_locale = locale; } - - await InvenTreeSettingsManager().setSelectedLocale(selected_locale); - - setState(() { - locale = selected_locale; - }); - - // Refresh the entire app locale - InvenTreeApp.of(context)?.setLocale(locale); - - // Clear the cached status label information - InvenTreeAPI().clearStatusCodeData(); } - ); - } + await InvenTreeSettingsManager().setSelectedLocale(selected_locale); + + setState(() { + locale = selected_locale; + }); + + // Refresh the entire app locale + InvenTreeApp.of(context)?.setLocale(locale); + + // Clear the cached status label information + InvenTreeAPI().clearStatusCodeData(); + }); + } @override Widget build(BuildContext context) { - String languageName = L10().languageDefault; if (locale != null) { - languageName = LocaleNames.of(context)!.nameOf(locale.toString()) ?? L10().languageDefault; + languageName = LocaleNames.of(context)!.nameOf(locale.toString()) ?? + L10().languageDefault; } IconData orientationIcon = Icons.screen_rotation; @@ -154,166 +150,163 @@ class _InvenTreeAppSettingsState extends State { } return Scaffold( - key: _settingsKey, - appBar: AppBar( - title: Text(L10().appSettings), - backgroundColor: COLOR_APP_BAR - ), - body: Container( - child: ListView( - children: [ - /* Sound Settings */ - Divider(height: 3), - ListTile( - title: Text( - L10().appSettings, - style: TextStyle(fontWeight: FontWeight.bold), - ), - leading: Icon(TablerIcons.device_mobile), + key: _settingsKey, + appBar: AppBar( + title: Text(L10().appSettings), backgroundColor: COLOR_APP_BAR), + body: Container( + child: ListView(children: [ + /* Sound Settings */ + Divider(height: 3), + ListTile( + title: Text( + L10().appSettings, + style: TextStyle(fontWeight: FontWeight.bold), ), - ListTile( + leading: Icon(TablerIcons.device_mobile), + ), + ListTile( title: Text(L10().darkMode), subtitle: Text(L10().darkModeEnable), leading: Icon(TablerIcons.sun_moon), trailing: Switch( - value: darkMode, - onChanged: (bool value) { - if (value) { - AdaptiveTheme.of(context).setDark(); - } else { - AdaptiveTheme.of(context).setLight(); - } - setState(() { - darkMode = value; - }); - } - ) - ), - GestureDetector( - child: ListTile( - title: Text(L10().orientation), - subtitle: Text(L10().orientationDetail), - leading: Icon(Icons.screen_rotation_alt), - trailing: Icon(orientationIcon), - ), - onTap: () async { - choiceDialog( - L10().orientation, - [ - ListTile( - leading: Icon(Icons.screen_rotation, color: screenOrientation == SCREEN_ORIENTATION_SYSTEM ? COLOR_ACTION : null), - title: Text(L10().orientationSystem), - ), - ListTile( - leading: Icon(Icons.screen_lock_portrait, color: screenOrientation == SCREEN_ORIENTATION_PORTRAIT ? COLOR_ACTION : null), - title: Text(L10().orientationPortrait), - ), - ListTile( - leading: Icon(Icons.screen_lock_landscape, color: screenOrientation == SCREEN_ORIENTATION_LANDSCAPE ? COLOR_ACTION : null), - title: Text(L10().orientationLandscape), - ) - ], - onSelected: (idx) async { - screenOrientation = idx as int; - - InvenTreeSettingsManager().setValue(INV_SCREEN_ORIENTATION, screenOrientation); - + value: darkMode, + onChanged: (bool value) { + if (value) { + AdaptiveTheme.of(context).setDark(); + } else { + AdaptiveTheme.of(context).setLight(); + } setState(() { + darkMode = value; }); - } - ); - }, + })), + GestureDetector( + child: ListTile( + title: Text(L10().orientation), + subtitle: Text(L10().orientationDetail), + leading: Icon(Icons.screen_rotation_alt), + trailing: Icon(orientationIcon), ), - ListTile( - title: Text(L10().labelPrinting), - subtitle: Text(L10().labelPrintingDetail), - leading: Icon(TablerIcons.printer), - trailing: Switch( + onTap: () async { + choiceDialog(L10().orientation, [ + ListTile( + leading: Icon(Icons.screen_rotation, + color: screenOrientation == SCREEN_ORIENTATION_SYSTEM + ? COLOR_ACTION + : null), + title: Text(L10().orientationSystem), + ), + ListTile( + leading: Icon(Icons.screen_lock_portrait, + color: screenOrientation == SCREEN_ORIENTATION_PORTRAIT + ? COLOR_ACTION + : null), + title: Text(L10().orientationPortrait), + ), + ListTile( + leading: Icon(Icons.screen_lock_landscape, + color: screenOrientation == SCREEN_ORIENTATION_LANDSCAPE + ? COLOR_ACTION + : null), + title: Text(L10().orientationLandscape), + ) + ], onSelected: (idx) async { + screenOrientation = idx as int; + + InvenTreeSettingsManager() + .setValue(INV_SCREEN_ORIENTATION, screenOrientation); + + setState(() {}); + }); + }, + ), + ListTile( + title: Text(L10().labelPrinting), + subtitle: Text(L10().labelPrintingDetail), + leading: Icon(TablerIcons.printer), + trailing: Switch( value: enableLabelPrinting, onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_ENABLE_LABEL_PRINTING, value); + InvenTreeSettingsManager() + .setValue(INV_ENABLE_LABEL_PRINTING, value); setState(() { enableLabelPrinting = value; }); - } - ), - ), - ListTile( - title: Text(L10().strictHttps), - subtitle: Text(L10().strictHttpsDetails), - leading: Icon(TablerIcons.lock), - trailing: Switch( - value: strictHttps, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_STRICT_HTTPS, value); - setState(() { - strictHttps = value; - }); - }, - ), - ), - ListTile( - title: Text(L10().language), - subtitle: Text(languageName), - leading: Icon(TablerIcons.language), - onTap: () async { - _selectLocale(context); + }), + ), + ListTile( + title: Text(L10().strictHttps), + subtitle: Text(L10().strictHttpsDetails), + leading: Icon(TablerIcons.lock), + trailing: Switch( + value: strictHttps, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_STRICT_HTTPS, value); + setState(() { + strictHttps = value; + }); }, ), - ListTile( - title: Text(L10().errorReportUpload), - subtitle: Text(L10().errorReportUploadDetails), - leading: Icon(TablerIcons.bug), - trailing: Switch( - value: reportErrors, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_REPORT_ERRORS, value); - setState(() { - reportErrors = value; - }); - }, - ), + ), + ListTile( + title: Text(L10().language), + subtitle: Text(languageName), + leading: Icon(TablerIcons.language), + onTap: () async { + _selectLocale(context); + }, + ), + ListTile( + title: Text(L10().errorReportUpload), + subtitle: Text(L10().errorReportUploadDetails), + leading: Icon(TablerIcons.bug), + trailing: Switch( + value: reportErrors, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_REPORT_ERRORS, value); + setState(() { + reportErrors = value; + }); + }, ), - ListTile( - title: Text( - L10().sounds, - style: TextStyle(fontWeight: FontWeight.bold), - ), - leading: Icon(TablerIcons.volume), + ), + ListTile( + title: Text( + L10().sounds, + style: TextStyle(fontWeight: FontWeight.bold), ), - Divider(), - ListTile( - title: Text(L10().serverError), - subtitle: Text(L10().soundOnServerError), - leading: Icon(TablerIcons.server), - trailing: Switch( - value: serverSounds, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_SOUNDS_SERVER, value); - setState(() { - serverSounds = value; - }); - }, - ), + leading: Icon(TablerIcons.volume), + ), + Divider(), + ListTile( + title: Text(L10().serverError), + subtitle: Text(L10().soundOnServerError), + leading: Icon(TablerIcons.server), + trailing: Switch( + value: serverSounds, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_SOUNDS_SERVER, value); + setState(() { + serverSounds = value; + }); + }, ), - ListTile( - title: Text(L10().barcodeTones), - subtitle: Text(L10().soundOnBarcodeAction), - leading: Icon(TablerIcons.qrcode), - trailing: Switch( - value: barcodeSounds, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_SOUNDS_BARCODE, value); - setState(() { - barcodeSounds = value; - }); - }, - ), + ), + ListTile( + title: Text(L10().barcodeTones), + subtitle: Text(L10().soundOnBarcodeAction), + leading: Icon(TablerIcons.qrcode), + trailing: Switch( + value: barcodeSounds, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_SOUNDS_BARCODE, value); + setState(() { + barcodeSounds = value; + }); + }, ), - Divider(height: 1), - ] - ) - ) - ); + ), + Divider(height: 1), + ]))); } -} \ No newline at end of file +} diff --git a/lib/settings/barcode_settings.dart b/lib/settings/barcode_settings.dart index 08a7bf60..5701fb96 100644 --- a/lib/settings/barcode_settings.dart +++ b/lib/settings/barcode_settings.dart @@ -7,97 +7,98 @@ import "package:inventree/app_colors.dart"; import "package:inventree/widget/dialogs.dart"; - class InvenTreeBarcodeSettingsWidget extends StatefulWidget { @override - _InvenTreeBarcodeSettingsState createState() => _InvenTreeBarcodeSettingsState(); + _InvenTreeBarcodeSettingsState createState() => + _InvenTreeBarcodeSettingsState(); } +class _InvenTreeBarcodeSettingsState + extends State { + _InvenTreeBarcodeSettingsState(); -class _InvenTreeBarcodeSettingsState extends State { + int barcodeScanDelay = 500; + int barcodeScanType = BARCODE_CONTROLLER_CAMERA; + bool barcodeScanSingle = false; - _InvenTreeBarcodeSettingsState(); + final TextEditingController _barcodeScanDelayController = + TextEditingController(); - int barcodeScanDelay = 500; - int barcodeScanType = BARCODE_CONTROLLER_CAMERA; - bool barcodeScanSingle = false; - - final TextEditingController _barcodeScanDelayController = TextEditingController(); - - @override - void initState() { - super.initState(); - loadSettings(); - } + @override + void initState() { + super.initState(); + loadSettings(); + } Future loadSettings() async { - barcodeScanDelay = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_DELAY, 500) as int; - barcodeScanType = await InvenTreeSettingsManager().getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int; - barcodeScanSingle = await InvenTreeSettingsManager().getBool(INV_BARCODE_SCAN_SINGLE, false); + barcodeScanDelay = await InvenTreeSettingsManager() + .getValue(INV_BARCODE_SCAN_DELAY, 500) as int; + barcodeScanType = await InvenTreeSettingsManager() + .getValue(INV_BARCODE_SCAN_TYPE, BARCODE_CONTROLLER_CAMERA) as int; + barcodeScanSingle = await InvenTreeSettingsManager() + .getBool(INV_BARCODE_SCAN_SINGLE, false); if (mounted) { - setState(() { - }); + setState(() {}); } } // Callback function to edit the barcode scan delay value // TODO: Next time any new settings are added, refactor this into a generic function Future _editBarcodeScanDelay(BuildContext context) async { - _barcodeScanDelayController.text = barcodeScanDelay.toString(); return showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(L10().barcodeScanDelay), - content: TextField( - onChanged: (value) {}, - controller: _barcodeScanDelayController, - keyboardType: TextInputType.number, - decoration: InputDecoration( - hintText: L10().barcodeScanDelayDetail, + context: context, + builder: (context) { + return AlertDialog( + title: Text(L10().barcodeScanDelay), + content: TextField( + onChanged: (value) {}, + controller: _barcodeScanDelayController, + keyboardType: TextInputType.number, + decoration: InputDecoration( + hintText: L10().barcodeScanDelayDetail, + ), ), - ), - actions: [ - MaterialButton( - color: Colors.red, - textColor: Colors.white, - child: Text(L10().cancel), - onPressed: () { - setState(() { - Navigator.pop(context); - }); - }, - ), - MaterialButton( - color: Colors.green, - textColor: Colors.white, - child: Text(L10().ok), - onPressed: () async { - int delay = int.tryParse(_barcodeScanDelayController.text) ?? barcodeScanDelay; + actions: [ + MaterialButton( + color: Colors.red, + textColor: Colors.white, + child: Text(L10().cancel), + onPressed: () { + setState(() { + Navigator.pop(context); + }); + }, + ), + MaterialButton( + color: Colors.green, + textColor: Colors.white, + child: Text(L10().ok), + onPressed: () async { + int delay = int.tryParse(_barcodeScanDelayController.text) ?? + barcodeScanDelay; - // Apply limits - if (delay < 100) delay = 100; - if (delay > 2500) delay = 2500; + // Apply limits + if (delay < 100) delay = 100; + if (delay > 2500) delay = 2500; - InvenTreeSettingsManager().setValue(INV_BARCODE_SCAN_DELAY, delay); - setState(() { - barcodeScanDelay = delay; - Navigator.pop(context); - }); - }, - ), - ], - ); - } - ); + InvenTreeSettingsManager() + .setValue(INV_BARCODE_SCAN_DELAY, delay); + setState(() { + barcodeScanDelay = delay; + Navigator.pop(context); + }); + }, + ), + ], + ); + }); } @override Widget build(BuildContext context) { - // Construct an icon for the barcode scanner input Widget? barcodeInputIcon; @@ -112,22 +113,18 @@ class _InvenTreeBarcodeSettingsState extends State { - _HomeScreenSettingsState(); - final GlobalKey<_HomeScreenSettingsState> _settingsKey = GlobalKey<_HomeScreenSettingsState>(); + final GlobalKey<_HomeScreenSettingsState> _settingsKey = + GlobalKey<_HomeScreenSettingsState>(); // Home screen settings bool homeShowSubscribed = true; @@ -32,24 +31,27 @@ class _HomeScreenSettingsState extends State { loadSettings(); } - Future loadSettings() async { - + Future loadSettings() async { // Load initial settings - homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; - homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, true) as bool; - homeShowSo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SO, true) as bool; - homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; - homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; - homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; + homeShowSubscribed = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; + homeShowPo = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_PO, true) as bool; + homeShowSo = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SO, true) as bool; + homeShowManufacturers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; + homeShowCustomers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; + homeShowSuppliers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; - setState(() { - }); + setState(() {}); } @override Widget build(BuildContext context) { - return Scaffold( key: _settingsKey, appBar: AppBar( @@ -57,67 +59,67 @@ class _HomeScreenSettingsState extends State { backgroundColor: COLOR_APP_BAR, ), body: Container( - child: ListView( - children: [ - ListTile( - title: Text(L10().homeShowSubscribed), - subtitle: Text(L10().homeShowSubscribedDescription), - leading: Icon(TablerIcons.bell), - trailing: Switch( - value: homeShowSubscribed, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SUBSCRIBED, value); - setState(() { - homeShowSubscribed = value; - }); - }, - ) - ), - ListTile( - title: Text(L10().homeShowPo), - subtitle: Text(L10().homeShowPoDescription), - leading: Icon(TablerIcons.shopping_cart), - trailing: Switch( - value: homeShowPo, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_HOME_SHOW_PO, value); - setState(() { - homeShowPo = value; - }); - }, - ), - ), - ListTile( - title: Text(L10().homeShowSo), - subtitle: Text(L10().homeShowSoDescription), - leading: Icon(TablerIcons.truck), - trailing: Switch( - value: homeShowSo, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SO, value); - setState(() { - homeShowSo = value; - }); - }, - ), - ), - ListTile( - title: Text(L10().homeShowSuppliers), - subtitle: Text(L10().homeShowSuppliersDescription), - leading: Icon(TablerIcons.building), - trailing: Switch( - value: homeShowSuppliers, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SUPPLIERS, value); - setState(() { - homeShowSuppliers = value; - }); - }, - ), - ), - // TODO: When these features are improved, add them back in! - // Currently, the company display does not provide any value - /* + child: ListView(children: [ + ListTile( + title: Text(L10().homeShowSubscribed), + subtitle: Text(L10().homeShowSubscribedDescription), + leading: Icon(TablerIcons.bell), + trailing: Switch( + value: homeShowSubscribed, + onChanged: (bool value) { + InvenTreeSettingsManager() + .setValue(INV_HOME_SHOW_SUBSCRIBED, value); + setState(() { + homeShowSubscribed = value; + }); + }, + )), + ListTile( + title: Text(L10().homeShowPo), + subtitle: Text(L10().homeShowPoDescription), + leading: Icon(TablerIcons.shopping_cart), + trailing: Switch( + value: homeShowPo, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_HOME_SHOW_PO, value); + setState(() { + homeShowPo = value; + }); + }, + ), + ), + ListTile( + title: Text(L10().homeShowSo), + subtitle: Text(L10().homeShowSoDescription), + leading: Icon(TablerIcons.truck), + trailing: Switch( + value: homeShowSo, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_HOME_SHOW_SO, value); + setState(() { + homeShowSo = value; + }); + }, + ), + ), + ListTile( + title: Text(L10().homeShowSuppliers), + subtitle: Text(L10().homeShowSuppliersDescription), + leading: Icon(TablerIcons.building), + trailing: Switch( + value: homeShowSuppliers, + onChanged: (bool value) { + InvenTreeSettingsManager() + .setValue(INV_HOME_SHOW_SUPPLIERS, value); + setState(() { + homeShowSuppliers = value; + }); + }, + ), + ), + // TODO: When these features are improved, add them back in! + // Currently, the company display does not provide any value + /* ListTile( title: Text(L10().homeShowManufacturers), subtitle: Text(L10().homeShowManufacturersDescription), @@ -133,23 +135,21 @@ class _HomeScreenSettingsState extends State { ), ), */ - ListTile( - title: Text(L10().homeShowCustomers), - subtitle: Text(L10().homeShowCustomersDescription), - leading: Icon(TablerIcons.user), - trailing: Switch( - value: homeShowCustomers, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_HOME_SHOW_CUSTOMERS, value); - setState(() { - homeShowCustomers = value; - }); - }, - ), - ), - ] - ) - ) - ); + ListTile( + title: Text(L10().homeShowCustomers), + subtitle: Text(L10().homeShowCustomersDescription), + leading: Icon(TablerIcons.user), + trailing: Switch( + value: homeShowCustomers, + onChanged: (bool value) { + InvenTreeSettingsManager() + .setValue(INV_HOME_SHOW_CUSTOMERS, value); + setState(() { + homeShowCustomers = value; + }); + }, + ), + ), + ]))); } -} \ No newline at end of file +} diff --git a/lib/settings/login.dart b/lib/settings/login.dart index 908d09a5..9f92ccfe 100644 --- a/lib/settings/login.dart +++ b/lib/settings/login.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -9,21 +8,16 @@ import "package:inventree/api.dart"; import "package:inventree/widget/dialogs.dart"; import "package:inventree/widget/progress.dart"; - class InvenTreeLoginWidget extends StatefulWidget { - const InvenTreeLoginWidget(this.profile) : super(); final UserProfile profile; @override _InvenTreeLoginState createState() => _InvenTreeLoginState(); - } - class _InvenTreeLoginState extends State { - final formKey = GlobalKey(); String username = ""; @@ -35,14 +29,12 @@ class _InvenTreeLoginState extends State { // Attempt login Future _doLogin(BuildContext context) async { - // Save form formKey.currentState?.save(); bool valid = formKey.currentState?.validate() ?? false; if (valid) { - // Dismiss the keyboard FocusScopeNode currentFocus = FocusScope.of(context); @@ -53,7 +45,8 @@ class _InvenTreeLoginState extends State { showLoadingOverlay(); // Attempt login - final response = await InvenTreeAPI().fetchToken(widget.profile, username, password); + final response = + await InvenTreeAPI().fetchToken(widget.profile, username, password); hideLoadingOverlay(); @@ -75,12 +68,10 @@ class _InvenTreeLoginState extends State { }); } } - } @override Widget build(BuildContext context) { - List before = [ ListTile( title: Text(L10().loginEnter), @@ -106,82 +97,77 @@ class _InvenTreeLoginState extends State { )); } return Scaffold( - appBar: AppBar( - title: Text(L10().login), - backgroundColor: COLOR_APP_BAR, - actions: [ - IconButton( - icon: Icon(TablerIcons.transition_right, color: COLOR_SUCCESS), - onPressed: () async { - _doLogin(context); - }, - ) - ] - ), - body: Form( - key: formKey, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...before, - TextFormField( - decoration: InputDecoration( - labelText: L10().username, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - hintText: L10().enterUsername - ), - initialValue: "", - keyboardType: TextInputType.text, - onSaved: (value) { - username = value?.trim() ?? ""; + appBar: AppBar( + title: Text(L10().login), + backgroundColor: COLOR_APP_BAR, + actions: [ + IconButton( + icon: Icon(TablerIcons.transition_right, color: COLOR_SUCCESS), + onPressed: () async { + _doLogin(context); }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().usernameEmpty; - } + ) + ]), + body: Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...before, + 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; - }, - ), - 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; - }); - }, - ), + return null; + }, ), - initialValue: "", - keyboardType: TextInputType.visiblePassword, - obscureText: _obscured, - onSaved: (value) { - password = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().passwordEmpty; - } + 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.visiblePassword, + obscureText: _obscured, + onSaved: (value) { + password = value?.trim() ?? ""; + }, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return L10().passwordEmpty; + } - return null; - } + return null; + }), + ...after, + ], ), - ...after, - ], - ), - padding: EdgeInsets.all(16), - ) - ) - ); - + padding: EdgeInsets.all(16), + ))); } - -} \ No newline at end of file +} diff --git a/lib/settings/part_settings.dart b/lib/settings/part_settings.dart index 5a8ccd23..5e1cb41b 100644 --- a/lib/settings/part_settings.dart +++ b/lib/settings/part_settings.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -6,15 +5,12 @@ import "package:inventree/l10.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/preferences.dart"; - class InvenTreePartSettingsWidget extends StatefulWidget { @override _InvenTreePartSettingsState createState() => _InvenTreePartSettingsState(); } - class _InvenTreePartSettingsState extends State { - _InvenTreePartSettingsState(); bool partShowParameters = true; @@ -32,117 +28,120 @@ class _InvenTreePartSettingsState extends State { } Future loadSettings() async { - partShowParameters = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PARAMETERS, true); - partShowBom = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_BOM, true); - partShowPricing = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PRICING, true); - 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); + partShowParameters = await InvenTreeSettingsManager() + .getBool(INV_PART_SHOW_PARAMETERS, true); + partShowBom = + await InvenTreeSettingsManager().getBool(INV_PART_SHOW_BOM, true); + partShowPricing = + await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PRICING, true); + 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(() { - }); + setState(() {}); } } @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(L10().partSettings), - backgroundColor: COLOR_APP_BAR - ), - body: Container( - child: ListView( - children: [ - ListTile( - title: Text(L10().parameters), - subtitle: Text(L10().parametersSettingDetail), - leading: Icon(TablerIcons.list), - trailing: Switch( - value: partShowParameters, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_PART_SHOW_PARAMETERS, value); - setState(() { - partShowParameters = value; - }); - }, - ), + appBar: AppBar( + title: Text(L10().partSettings), backgroundColor: COLOR_APP_BAR), + body: Container( + child: ListView(children: [ + ListTile( + title: Text(L10().parameters), + subtitle: Text(L10().parametersSettingDetail), + leading: Icon(TablerIcons.list), + trailing: Switch( + value: partShowParameters, + onChanged: (bool value) { + InvenTreeSettingsManager() + .setValue(INV_PART_SHOW_PARAMETERS, value); + setState(() { + partShowParameters = value; + }); + }, ), - ListTile( - title: Text(L10().bom), - subtitle: Text(L10().bomEnable), - leading: Icon(TablerIcons.list), - trailing: Switch( - value: partShowBom, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_PART_SHOW_BOM, value); - setState(() { - partShowBom = value; - }); - }, - ), + ), + ListTile( + title: Text(L10().bom), + subtitle: Text(L10().bomEnable), + leading: Icon(TablerIcons.list), + trailing: Switch( + value: partShowBom, + onChanged: (bool value) { + InvenTreeSettingsManager().setValue(INV_PART_SHOW_BOM, value); + setState(() { + partShowBom = value; + }); + }, ), - ListTile( - title: Text(L10().partPricing), - subtitle: Text(L10().partPricingSettingDetail), - leading: Icon(TablerIcons.currency_dollar), - trailing: Switch( - value: partShowPricing, - onChanged: (bool value) { - InvenTreeSettingsManager().setValue(INV_PART_SHOW_PRICING, value); - setState(() { - partShowPricing = value; - }); - }, - ), + ), + ListTile( + title: Text(L10().partPricing), + subtitle: Text(L10().partPricingSettingDetail), + leading: Icon(TablerIcons.currency_dollar), + trailing: Switch( + value: partShowPricing, + onChanged: (bool value) { + InvenTreeSettingsManager() + .setValue(INV_PART_SHOW_PRICING, value); + setState(() { + partShowPricing = value; + }); + }, ), - Divider(), - 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; - }); - }, - ), + ), + Divider(), + 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().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( + ), + 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); + InvenTreeSettingsManager() + .setValue(INV_STOCK_CONFIRM_SCAN, value); setState(() { stockConfirmScan = value; }); - } - ), - ) - ] - ) - ) - ); + }), + ) + ]))); } -} \ No newline at end of file +} diff --git a/lib/settings/purchase_order_settings.dart b/lib/settings/purchase_order_settings.dart index 888b89b1..d93aa5db 100644 --- a/lib/settings/purchase_order_settings.dart +++ b/lib/settings/purchase_order_settings.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/app_colors.dart"; @@ -6,15 +5,14 @@ import "package:inventree/app_colors.dart"; import "package:inventree/l10.dart"; import "package:inventree/preferences.dart"; - class InvenTreePurchaseOrderSettingsWidget extends StatefulWidget { @override - _InvenTreePurchaseOrderSettingsState createState() => _InvenTreePurchaseOrderSettingsState(); + _InvenTreePurchaseOrderSettingsState createState() => + _InvenTreePurchaseOrderSettingsState(); } - -class _InvenTreePurchaseOrderSettingsState extends State { - +class _InvenTreePurchaseOrderSettingsState + extends State { _InvenTreePurchaseOrderSettingsState(); bool poEnable = true; @@ -30,12 +28,13 @@ class _InvenTreePurchaseOrderSettingsState extends State loadSettings() async { poEnable = await InvenTreeSettingsManager().getBool(INV_PO_ENABLE, true); - poShowCamera = await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true); - poConfirmScan = await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true); + poShowCamera = + await InvenTreeSettingsManager().getBool(INV_PO_SHOW_CAMERA, true); + poConfirmScan = + await InvenTreeSettingsManager().getBool(INV_PO_CONFIRM_SCAN, true); if (mounted) { - setState(() { - }); + setState(() {}); } } @@ -47,53 +46,49 @@ class _InvenTreePurchaseOrderSettingsState extends State openLink(String url) async { - final link = Uri.parse(url); if (await canLaunchUrl(link)) { @@ -52,22 +46,21 @@ class CreditsWidget extends StatelessWidget { } @override - Widget build (BuildContext context) { + Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(L10().credits), - backgroundColor: COLOR_APP_BAR, - ), - body: Markdown( - selectable: false, - data: credits, - onTapLink: (url, href, title) { - var link = href ?? ""; - if (link.isNotEmpty) { - openLink(link); - } - }, - ) - ); + appBar: AppBar( + title: Text(L10().credits), + backgroundColor: COLOR_APP_BAR, + ), + body: Markdown( + selectable: false, + data: credits, + onTapLink: (url, href, title) { + var link = href ?? ""; + if (link.isNotEmpty) { + openLink(link); + } + }, + )); } -} \ No newline at end of file +} diff --git a/lib/settings/sales_order_settings.dart b/lib/settings/sales_order_settings.dart index cbeacb66..f9aa8333 100644 --- a/lib/settings/sales_order_settings.dart +++ b/lib/settings/sales_order_settings.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -6,15 +5,14 @@ import "package:inventree/l10.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/preferences.dart"; - class InvenTreeSalesOrderSettingsWidget extends StatefulWidget { @override - _InvenTreeSalesOrderSettingsState createState() => _InvenTreeSalesOrderSettingsState(); + _InvenTreeSalesOrderSettingsState createState() => + _InvenTreeSalesOrderSettingsState(); } - -class _InvenTreeSalesOrderSettingsState extends State { - +class _InvenTreeSalesOrderSettingsState + extends State { _InvenTreeSalesOrderSettingsState(); bool soEnable = true; @@ -29,11 +27,11 @@ class _InvenTreeSalesOrderSettingsState extends State loadSettings() async { soEnable = await InvenTreeSettingsManager().getBool(INV_SO_ENABLE, true); - soShowCamera = await InvenTreeSettingsManager().getBool(INV_SO_SHOW_CAMERA, true); + soShowCamera = + await InvenTreeSettingsManager().getBool(INV_SO_SHOW_CAMERA, true); if (mounted) { - setState(() { - }); + setState(() {}); } } @@ -45,39 +43,35 @@ class _InvenTreeSalesOrderSettingsState extends State _InvenTreeSelectServerState(); } - class _InvenTreeSelectServerState extends State { - _InvenTreeSelectServerState() { _reload(); } - final GlobalKey<_InvenTreeSelectServerState> _loginKey = GlobalKey<_InvenTreeSelectServerState>(); + final GlobalKey<_InvenTreeSelectServerState> _loginKey = + GlobalKey<_InvenTreeSelectServerState>(); List profiles = []; - Future _reload() async { - + Future _reload() async { profiles = await UserProfileDBManager().getAllProfiles(); if (!mounted) { return; } - setState(() { - }); + setState(() {}); } /* * Logout the selected profile (delete the stored token) */ - Future _logoutProfile(BuildContext context, {UserProfile? userProfile}) async { - + Future _logoutProfile(BuildContext context, + {UserProfile? userProfile}) async { if (userProfile != null) { userProfile.token = ""; await UserProfileDBManager().updateProfile(userProfile); @@ -54,26 +50,23 @@ class _InvenTreeSelectServerState extends State { InvenTreeAPI().disconnectFromServer(); _reload(); - } /* * Edit the selected profile */ - void _editProfile(BuildContext context, {UserProfile? userProfile, bool createNew = false}) { - + void _editProfile(BuildContext context, + {UserProfile? userProfile, bool createNew = false}) { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ProfileEditWidget(userProfile) - ) - ).then((context) { + context, + MaterialPageRoute( + builder: (context) => ProfileEditWidget(userProfile))) + .then((context) { _reload(); }); } - Future _selectProfile(BuildContext context, UserProfile profile) async { - + Future _selectProfile(BuildContext context, UserProfile profile) async { // Disconnect InvenTree InvenTreeAPI().disconnectFromServer(); @@ -94,9 +87,11 @@ class _InvenTreeSelectServerState extends State { // First check if the profile has an associate token if (!prf.hasToken) { // Redirect user to login screen - Navigator.push(context, - MaterialPageRoute(builder: (context) => InvenTreeLoginWidget(profile)) - ).then((value) async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InvenTreeLoginWidget(profile))) + .then((value) async { _reload(); // Reload profile prf = await UserProfileDBManager().getProfileByKey(key); @@ -125,8 +120,7 @@ class _InvenTreeSelectServerState extends State { _reload(); } - Future _deleteProfile(UserProfile profile) async { - + Future _deleteProfile(UserProfile profile) async { await UserProfileDBManager().deleteProfile(profile); if (!mounted) { @@ -135,13 +129,13 @@ class _InvenTreeSelectServerState extends State { _reload(); - if (InvenTreeAPI().isConnected() && profile.key == (InvenTreeAPI().profile?.key ?? "")) { + if (InvenTreeAPI().isConnected() && + profile.key == (InvenTreeAPI().profile?.key ?? "")) { InvenTreeAPI().disconnectFromServer(); } } Widget? _getProfileIcon(UserProfile profile) { - // Not selected? No icon for you! if (!profile.selected) return null; @@ -152,10 +146,7 @@ class _InvenTreeSelectServerState extends State { // Reflect the connection status of the server if (InvenTreeAPI().isConnected()) { - return Icon( - TablerIcons.circle_check, - color: COLOR_SUCCESS - ); + return Icon(TablerIcons.circle_check, color: COLOR_SUCCESS); } else if (InvenTreeAPI().isConnecting()) { return Spinner( icon: TablerIcons.loader_2, @@ -171,7 +162,6 @@ class _InvenTreeSelectServerState extends State { @override Widget build(BuildContext context) { - List children = []; if (profiles.isNotEmpty) { @@ -182,84 +172,76 @@ class _InvenTreeSelectServerState extends State { title: Text( profile.name, ), - tileColor: profile.selected ? Theme.of(context).secondaryHeaderColor : null, + tileColor: + profile.selected ? Theme.of(context).secondaryHeaderColor : null, subtitle: Text("${profile.server}"), - leading: profile.hasToken ? Icon(TablerIcons.user_check, color: COLOR_SUCCESS) : Icon(TablerIcons.user_cancel, color: COLOR_WARNING), + leading: profile.hasToken + ? Icon(TablerIcons.user_check, color: COLOR_SUCCESS) + : Icon(TablerIcons.user_cancel, color: COLOR_WARNING), trailing: _getProfileIcon(profile), onTap: () { _selectProfile(context, profile); }, onLongPress: () { - OneContext().showDialog( - builder: (BuildContext context) { - return SimpleDialog( - title: Text(profile.name), - children: [ - Divider(), - SimpleDialogOption( - onPressed: () { - Navigator.of(context).pop(); - _selectProfile(context, profile); - }, - child: ListTile( - title: Text(L10().profileConnect), - leading: Icon(TablerIcons.server), - ) - ), - SimpleDialogOption( - onPressed: () { - Navigator.of(context).pop(); - _editProfile(context, userProfile: profile); - }, - child: ListTile( + OneContext().showDialog(builder: (BuildContext context) { + return SimpleDialog( + title: Text(profile.name), + children: [ + Divider(), + SimpleDialogOption( + onPressed: () { + Navigator.of(context).pop(); + _selectProfile(context, profile); + }, + child: ListTile( + title: Text(L10().profileConnect), + leading: Icon(TablerIcons.server), + )), + SimpleDialogOption( + onPressed: () { + Navigator.of(context).pop(); + _editProfile(context, userProfile: profile); + }, + child: ListTile( title: Text(L10().profileEdit), - leading: Icon(TablerIcons.edit) - ) - ), - SimpleDialogOption( - onPressed: () { - Navigator.of(context).pop(); - _logoutProfile(context, userProfile: profile); - }, - child: ListTile( - title: Text(L10().profileLogout), - leading: Icon(TablerIcons.logout), - ) - ), - Divider(), - SimpleDialogOption( - onPressed: () { - Navigator.of(context).pop(); - // Navigator.of(context, rootNavigator: true).pop(); - confirmationDialog( - L10().delete, - L10().profileDelete + "?", + leading: Icon(TablerIcons.edit))), + SimpleDialogOption( + onPressed: () { + Navigator.of(context).pop(); + _logoutProfile(context, userProfile: profile); + }, + child: ListTile( + title: Text(L10().profileLogout), + leading: Icon(TablerIcons.logout), + )), + Divider(), + SimpleDialogOption( + onPressed: () { + Navigator.of(context).pop(); + // Navigator.of(context, rootNavigator: true).pop(); + confirmationDialog( + L10().delete, L10().profileDelete + "?", color: Colors.red, - icon: TablerIcons.trash, - onAccept: () { - _deleteProfile(profile); - } - ); - }, - child: ListTile( - title: Text(L10().profileDelete, style: TextStyle(color: Colors.red)), - leading: Icon(TablerIcons.trash, color: Colors.red), - ) - ) - ], - ); - } - ); + icon: TablerIcons.trash, onAccept: () { + _deleteProfile(profile); + }); + }, + child: ListTile( + title: Text(L10().profileDelete, + style: TextStyle(color: Colors.red)), + leading: Icon(TablerIcons.trash, color: Colors.red), + )) + ], + ); + }); }, )); } } else { // No profile available! - children.add( - ListTile( - title: Text(L10().profileNone), - ) - ); + children.add(ListTile( + title: Text(L10().profileNone), + )); } return Scaffold( @@ -277,23 +259,18 @@ class _InvenTreeSelectServerState extends State { ], ), body: Container( - child: ListView( - children: ListTile.divideTiles( - context: context, - tiles: children - ).toList(), - ) - ), + child: ListView( + children: + ListTile.divideTiles(context: context, tiles: children).toList(), + )), ); } } - /* * Widget for editing server details */ class ProfileEditWidget extends StatefulWidget { - const ProfileEditWidget(this.profile) : super(); final UserProfile? profile; @@ -303,7 +280,6 @@ class ProfileEditWidget extends StatefulWidget { } class _ProfileEditState extends State { - _ProfileEditState() : super(); final formKey = GlobalKey(); @@ -314,120 +290,117 @@ class _ProfileEditState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - backgroundColor: COLOR_APP_BAR, - title: Text(widget.profile == null ? L10().profileAdd : L10().profileEdit), - actions: [ - IconButton( - icon: Icon(TablerIcons.circle_check), - onPressed: () async { - if (formKey.currentState!.validate()) { - formKey.currentState!.save(); + appBar: AppBar( + backgroundColor: COLOR_APP_BAR, + title: Text( + widget.profile == null ? L10().profileAdd : L10().profileEdit), + actions: [ + IconButton( + icon: Icon(TablerIcons.circle_check), + onPressed: () async { + if (formKey.currentState!.validate()) { + formKey.currentState!.save(); - UserProfile? prf = widget.profile; + UserProfile? prf = widget.profile; - if (prf == null) { - UserProfile profile = UserProfile( - name: name, - server: server, - ); + if (prf == null) { + UserProfile profile = UserProfile( + name: name, + server: server, + ); - await UserProfileDBManager().addProfile(profile); - } else { - - prf.name = name; - prf.server = server; - - await UserProfileDBManager().updateProfile(prf); - } - - // Close the window - Navigator.of(context).pop(); - } - }, - ) - ] - ), - body: Form( - key: formKey, - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - decoration: InputDecoration( - labelText: L10().profileName, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - ), - initialValue: widget.profile?.name ?? "", - maxLines: 1, - keyboardType: TextInputType.text, - onSaved: (value) { - name = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().valueCannotBeEmpty; - } - - return null; - } - ), - TextFormField( - decoration: InputDecoration( - labelText: L10().server, - labelStyle: TextStyle(fontWeight: FontWeight.bold), - hintText: "http[s]://:", - ), - initialValue: widget.profile?.server ?? "", - keyboardType: TextInputType.url, - onSaved: (value) { - server = value?.trim() ?? ""; - }, - validator: (value) { - if (value == null || value.trim().isEmpty) { - return L10().serverEmpty; - } - - value = value.trim(); - - // Spaces are bad - if (value.contains(" ")) { - return L10().invalidHost; - } - - if (!value.startsWith("http:") && !value.startsWith("https:")) { - // return L10().serverStart; - } - - Uri? _uri = Uri.tryParse(value); - - if (_uri == null || _uri.host.isEmpty) { - return L10().invalidHost; - } else { - Uri uri = Uri.parse(value); - - if (uri.hasScheme) { - if (!["http", "https"].contains(uri.scheme.toLowerCase())) { - return L10().serverStart; - } + await UserProfileDBManager().addProfile(profile); } else { - return L10().invalidHost; + prf.name = name; + prf.server = server; + + await UserProfileDBManager().updateProfile(prf); } + + // Close the window + Navigator.of(context).pop(); } - - // Everything is OK - return null; }, - ), - ] - ), - padding: EdgeInsets.all(16), - ), - ) - ); - } + ) + ]), + body: Form( + key: formKey, + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + decoration: InputDecoration( + labelText: L10().profileName, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + ), + initialValue: widget.profile?.name ?? "", + maxLines: 1, + keyboardType: TextInputType.text, + onSaved: (value) { + name = value?.trim() ?? ""; + }, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return L10().valueCannotBeEmpty; + } -} \ No newline at end of file + return null; + }), + TextFormField( + decoration: InputDecoration( + labelText: L10().server, + labelStyle: TextStyle(fontWeight: FontWeight.bold), + hintText: "http[s]://:", + ), + initialValue: widget.profile?.server ?? "", + keyboardType: TextInputType.url, + onSaved: (value) { + server = value?.trim() ?? ""; + }, + validator: (value) { + if (value == null || value.trim().isEmpty) { + return L10().serverEmpty; + } + + value = value.trim(); + + // Spaces are bad + if (value.contains(" ")) { + return L10().invalidHost; + } + + if (!value.startsWith("http:") && + !value.startsWith("https:")) { + // return L10().serverStart; + } + + Uri? _uri = Uri.tryParse(value); + + if (_uri == null || _uri.host.isEmpty) { + return L10().invalidHost; + } else { + Uri uri = Uri.parse(value); + + if (uri.hasScheme) { + if (!["http", "https"] + .contains(uri.scheme.toLowerCase())) { + return L10().serverStart; + } + } else { + return L10().invalidHost; + } + } + + // Everything is OK + return null; + }, + ), + ]), + padding: EdgeInsets.all(16), + ), + )); + } +} diff --git a/lib/settings/settings.dart b/lib/settings/settings.dart index 30f4f2b4..91646c8f 100644 --- a/lib/settings/settings.dart +++ b/lib/settings/settings.dart @@ -16,15 +16,11 @@ import "package:inventree/settings/sales_order_settings.dart"; // InvenTree settings view class InvenTreeSettingsWidget extends StatefulWidget { - @override _InvenTreeSettingsState createState() => _InvenTreeSettingsState(); - } - class _InvenTreeSettingsState extends State { - final _scaffoldKey = GlobalKey(); /* @@ -40,80 +36,96 @@ class _InvenTreeSettingsState extends State { @override Widget build(BuildContext context) { return Scaffold( - key: _scaffoldKey, - appBar: AppBar( - title: Text(L10().settings), - backgroundColor: COLOR_APP_BAR, - ), - body: Center( - child: ListView( - children: [ - ListTile( - title: Text(L10().server), - subtitle: Text(L10().configureServer), - leading: Icon(TablerIcons.server, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSelectServerWidget())); - }, - ), - Divider(), - ListTile( - title: Text(L10().appSettings), - subtitle: Text(L10().appSettingsDetails), - leading: Icon(TablerIcons.settings, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeAppSettingsWidget())); - } - ), - ListTile( - title: Text(L10().homeScreen), - subtitle: Text(L10().homeScreenSettings), - leading: Icon(TablerIcons.home, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => HomeScreenSettingsWidget())); - } - ), - ListTile( - title: Text(L10().barcodes), - subtitle: Text(L10().barcodeSettings), - leading: Icon(TablerIcons.barcode, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeBarcodeSettingsWidget())); - } - ), - ListTile( - title: Text(L10().part), - subtitle: Text(L10().partSettings), - leading: Icon(TablerIcons.box, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePartSettingsWidget())); - } - ), - ListTile( - title: Text(L10().purchaseOrder), - subtitle: Text(L10().purchaseOrderSettings), - leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePurchaseOrderSettingsWidget())); - }, - ), - ListTile( - title: Text(L10().salesOrder), - subtitle: Text(L10().salesOrderSettings), - leading: Icon(TablerIcons.truck, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSalesOrderSettingsWidget())); - }, - ), - Divider(), - ListTile( - title: Text(L10().about), - leading: Icon(TablerIcons.info_circle, color: COLOR_ACTION), - onTap: _about, - ) - ] - ) - ) - ); + key: _scaffoldKey, + appBar: AppBar( + title: Text(L10().settings), + backgroundColor: COLOR_APP_BAR, + ), + body: Center( + child: ListView(children: [ + ListTile( + title: Text(L10().server), + subtitle: Text(L10().configureServer), + leading: Icon(TablerIcons.server, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InvenTreeSelectServerWidget())); + }, + ), + Divider(), + ListTile( + title: Text(L10().appSettings), + subtitle: Text(L10().appSettingsDetails), + leading: Icon(TablerIcons.settings, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InvenTreeAppSettingsWidget())); + }), + ListTile( + title: Text(L10().homeScreen), + subtitle: Text(L10().homeScreenSettings), + leading: Icon(TablerIcons.home, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => HomeScreenSettingsWidget())); + }), + ListTile( + title: Text(L10().barcodes), + subtitle: Text(L10().barcodeSettings), + leading: Icon(TablerIcons.barcode, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + InvenTreeBarcodeSettingsWidget())); + }), + ListTile( + title: Text(L10().part), + subtitle: Text(L10().partSettings), + leading: Icon(TablerIcons.box, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => InvenTreePartSettingsWidget())); + }), + ListTile( + title: Text(L10().purchaseOrder), + subtitle: Text(L10().purchaseOrderSettings), + leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + InvenTreePurchaseOrderSettingsWidget())); + }, + ), + ListTile( + title: Text(L10().salesOrder), + subtitle: Text(L10().salesOrderSettings), + leading: Icon(TablerIcons.truck, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + InvenTreeSalesOrderSettingsWidget())); + }, + ), + Divider(), + ListTile( + title: Text(L10().about), + leading: Icon(TablerIcons.info_circle, color: COLOR_ACTION), + onTap: _about, + ) + ]))); } -} \ No newline at end of file +} diff --git a/lib/user_profile.dart b/lib/user_profile.dart index 9d17b065..76d9d54c 100644 --- a/lib/user_profile.dart +++ b/lib/user_profile.dart @@ -1,11 +1,9 @@ - import "package:sembast/sembast.dart"; import "package:inventree/helpers.dart"; import "package:inventree/preferences.dart"; class UserProfile { - UserProfile({ this.key, this.name = "", @@ -14,13 +12,15 @@ class UserProfile { this.selected = false, }); - factory UserProfile.fromJson(int key, Map json, bool isSelected) => UserProfile( - key: key, - name: (json["name"] ?? "") as String, - server: (json["server"] ?? "") as String, - token: (json["token"] ?? "") as String, - selected: isSelected, - ); + factory UserProfile.fromJson( + int key, Map json, bool isSelected) => + UserProfile( + key: key, + name: (json["name"] ?? "") as String, + server: (json["server"] ?? "") as String, + token: (json["token"] ?? "") as String, + selected: isSelected, + ); // Return true if this profile has a token bool get hasToken => token.isNotEmpty; @@ -43,10 +43,10 @@ class UserProfile { int user_id = -1; Map toJson() => { - "name": name, - "server": server, - "token": token, - }; + "name": name, + "server": server, + "token": token, + }; @override String toString() { @@ -58,7 +58,6 @@ class UserProfile { * Class for storing and managing user (server) profiles */ class UserProfileDBManager { - final store = StoreRef("profiles"); Future get _db async => InvenTreePreferencesDB.instance.database; @@ -67,7 +66,6 @@ class UserProfileDBManager { * Check if a profile with the specified name exists in the database */ Future profileNameExists(String name) async { - final profiles = await getAllProfiles(); for (var prf in profiles) { @@ -84,9 +82,9 @@ class UserProfileDBManager { * Add a new UserProfile to the profiles database. */ Future addProfile(UserProfile profile) async { - if (profile.name.isEmpty) { - debug("addProfile() : Profile missing required values - not adding to database"); + debug( + "addProfile() : Profile missing required values - not adding to database"); return false; } @@ -113,7 +111,6 @@ class UserProfileDBManager { * The unique integer is used to determine if the profile already exists. */ Future updateProfile(UserProfile profile) async { - // Prevent invalid profile data from being updated if (profile.name.isEmpty) { debug("updateProfile() : Profile missing required values - not updating"); @@ -144,15 +141,14 @@ class UserProfileDBManager { * The key of the UserProfile should match the "selected" property */ Future getSelectedProfile() async { - final selected = await store.record("selected").get(await _db); final profiles = await store.find(await _db); - debug("getSelectedProfile() : ${profiles.length} profiles available - selected = ${selected}"); + debug( + "getSelectedProfile() : ${profiles.length} profiles available - selected = ${selected}"); for (int idx = 0; idx < profiles.length; idx++) { - if (profiles[idx].key is int && profiles[idx].key == selected) { return UserProfile.fromJson( profiles[idx].key! as int, @@ -169,7 +165,6 @@ class UserProfileDBManager { * Return all user profile objects */ Future> getAllProfiles() async { - final selected = await store.record("selected").get(await _db); final profiles = await store.find(await _db); @@ -177,25 +172,22 @@ class UserProfileDBManager { List profileList = []; for (int idx = 0; idx < profiles.length; idx++) { - if (profiles[idx].key is int) { - profileList.add( - UserProfile.fromJson( - profiles[idx].key! as int, - profiles[idx].value! as Map, - profiles[idx].key == selected, - ) - ); + profileList.add(UserProfile.fromJson( + profiles[idx].key! as int, + profiles[idx].value! as Map, + profiles[idx].key == selected, + )); } } // If there are no available profiles, create a demo profile if (profileList.isEmpty) { - bool added = await InvenTreeSettingsManager().getBool("demo_profile_added", false); + bool added = + await InvenTreeSettingsManager().getBool("demo_profile_added", false); // Don't add a new profile if we have added it previously if (!added) { - await InvenTreeSettingsManager().setValue("demo_profile_added", true); UserProfile demoProfile = UserProfile( @@ -212,7 +204,6 @@ class UserProfileDBManager { return profileList; } - /* * Retrieve a profile by key (or null if no match exists) */ @@ -231,7 +222,6 @@ class UserProfileDBManager { return prf; } - /* * Retrieve a profile by name (or null if no match exists) */ diff --git a/lib/widget/attachment_widget.dart b/lib/widget/attachment_widget.dart index f92fbb69..9ab031ad 100644 --- a/lib/widget/attachment_widget.dart +++ b/lib/widget/attachment_widget.dart @@ -1,4 +1,3 @@ - import "dart:io"; import "package:flutter/material.dart"; @@ -17,7 +16,6 @@ import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/refreshable_state.dart"; - /* * A generic widget for displaying a list of attachments. * @@ -25,8 +23,9 @@ import "package:inventree/widget/refreshable_state.dart"; * we pass a subclassed instance of the InvenTreeAttachment model. */ class AttachmentWidget extends StatefulWidget { - - const AttachmentWidget(this.attachmentClass, this.modelId, this.imagePrefix, this.hasUploadPermission) : super(); + const AttachmentWidget(this.attachmentClass, this.modelId, this.imagePrefix, + this.hasUploadPermission) + : super(); final InvenTreeAttachment attachmentClass; final int modelId; @@ -37,9 +36,7 @@ class AttachmentWidget extends StatefulWidget { _AttachmentWidgetState createState() => _AttachmentWidgetState(); } - class _AttachmentWidgetState extends RefreshableState { - _AttachmentWidgetState(); List attachments = []; @@ -53,42 +50,37 @@ class _AttachmentWidgetState extends RefreshableState { return [ IconButton( - icon: Icon(TablerIcons.camera), - onPressed: () async { - widget.attachmentClass.uploadImage( - widget.modelId, - prefix: widget.imagePrefix, - ); - FilePickerDialog.pickImageFromCamera().then((File? file) { - upload(context, file).then((_) { - refresh(context); + icon: Icon(TablerIcons.camera), + onPressed: () async { + widget.attachmentClass.uploadImage( + widget.modelId, + prefix: widget.imagePrefix, + ); + FilePickerDialog.pickImageFromCamera().then((File? file) { + upload(context, file).then((_) { + refresh(context); + }); }); - }); - } - ), + }), IconButton( - icon: Icon(TablerIcons.file_upload), - onPressed: () async { - FilePickerDialog.pickFileFromDevice().then((File? file) { - upload(context, file).then((_) { - refresh(context); + icon: Icon(TablerIcons.file_upload), + onPressed: () async { + FilePickerDialog.pickFileFromDevice().then((File? file) { + upload(context, file).then((_) { + refresh(context); + }); }); - }); - } - ) + }) ]; } Future upload(BuildContext context, File? file) async { - if (file == null) return; showLoadingOverlay(); - final bool result = await widget.attachmentClass.uploadAttachment( - file, - widget.modelId - ); + final bool result = + await widget.attachmentClass.uploadAttachment(file, widget.modelId); hideLoadingOverlay(); @@ -101,82 +93,69 @@ class _AttachmentWidgetState extends RefreshableState { refresh(context); } - - Future editAttachment(BuildContext context, InvenTreeAttachment attachment) async - { - attachment.editForm(context, L10().editAttachment).then((result) => { - refresh(context) - }); + Future editAttachment( + BuildContext context, InvenTreeAttachment attachment) async { + attachment + .editForm(context, L10().editAttachment) + .then((result) => {refresh(context)}); } /* * Delete the specified attachment */ - Future deleteAttachment(BuildContext context, InvenTreeAttachment attachment) async { - + Future deleteAttachment( + BuildContext context, InvenTreeAttachment attachment) async { final bool result = await attachment.delete(); - showSnackIcon( - result ? L10().deleteSuccess : L10().deleteFailed, - success: result - ); + showSnackIcon(result ? L10().deleteSuccess : L10().deleteFailed, + success: result); refresh(context); - } /* * Display an option context menu for the selected attachment */ - Future showOptionsMenu(BuildContext context, InvenTreeAttachment attachment) async { - - OneContext().showDialog( - builder: (BuildContext ctx) { - return SimpleDialog( - title: Text(L10().attachments), - children: [ - Divider(), - SimpleDialogOption( - onPressed: () async { - OneContext().popDialog(); - editAttachment(context, attachment); - }, - child: ListTile( - title: Text(L10().edit), - leading: Icon(TablerIcons.edit), - ) - ), - SimpleDialogOption( - onPressed: () async { - OneContext().popDialog(); - deleteAttachment(context, attachment); - }, - child: ListTile( - title: Text(L10().delete), - leading: Icon(TablerIcons.trash, color: COLOR_DANGER), - ) - ) - ] - ); - } - ); + Future showOptionsMenu( + BuildContext context, InvenTreeAttachment attachment) async { + OneContext().showDialog(builder: (BuildContext ctx) { + return SimpleDialog(title: Text(L10().attachments), children: [ + Divider(), + SimpleDialogOption( + onPressed: () async { + OneContext().popDialog(); + editAttachment(context, attachment); + }, + child: ListTile( + title: Text(L10().edit), + leading: Icon(TablerIcons.edit), + )), + SimpleDialogOption( + onPressed: () async { + OneContext().popDialog(); + deleteAttachment(context, attachment); + }, + child: ListTile( + title: Text(L10().delete), + leading: Icon(TablerIcons.trash, color: COLOR_DANGER), + )) + ]); + }); } @override Future request(BuildContext context) async { - Map filters = {}; if (InvenTreeAPI().supportsModernAttachments) { filters["model_type"] = widget.attachmentClass.REF_MODEL_TYPE; filters["model_id"] = widget.modelId.toString(); } else { - filters[widget.attachmentClass.REFERENCE_FIELD] = widget.modelId.toString(); + filters[widget.attachmentClass.REFERENCE_FIELD] = + widget.modelId.toString(); } - await widget.attachmentClass.list( - filters: filters - ).then((var results) { + await widget.attachmentClass.list(filters: filters).then((var results) { attachments.clear(); for (var result in results) { @@ -186,18 +165,15 @@ class _AttachmentWidgetState extends RefreshableState { } }); - setState(() { - }); + setState(() {}); } @override List getTiles(BuildContext context) { - List tiles = []; // An "attachment" can either be a file, or a URL for (var attachment in attachments) { - if (attachment.filename.isNotEmpty) { tiles.add(ListTile( title: Text(attachment.filename), @@ -208,13 +184,11 @@ class _AttachmentWidgetState extends RefreshableState { await attachment.downloadAttachment(); hideLoadingOverlay(); }, - onLongPress: () { + onLongPress: () { showOptionsMenu(context, attachment); }, )); - } - - else if (attachment.link.isNotEmpty) { + } else if (attachment.link.isNotEmpty) { tiles.add(ListTile( title: Text(attachment.link), subtitle: Text(attachment.comment), @@ -225,7 +199,7 @@ class _AttachmentWidgetState extends RefreshableState { await launchUrl(uri); } }, - onLongPress: () { + onLongPress: () { showOptionsMenu(context, attachment); }, )); diff --git a/lib/widget/back.dart b/lib/widget/back.dart index 27a9c23a..f5d342ae 100644 --- a/lib/widget/back.dart +++ b/lib/widget/back.dart @@ -6,7 +6,6 @@ import "package:flutter/material.dart"; * Long-pressing on this will return the user to the home screen */ Widget backButton(BuildContext context, GlobalKey key) { - return GestureDetector( onLongPress: () { // Display the menu @@ -21,4 +20,4 @@ Widget backButton(BuildContext context, GlobalKey key) { }, ), ); -} \ No newline at end of file +} diff --git a/lib/widget/company/company_detail.dart b/lib/widget/company/company_detail.dart index 10073869..19153c57 100644 --- a/lib/widget/company/company_detail.dart +++ b/lib/widget/company/company_detail.dart @@ -16,24 +16,19 @@ import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/company/supplier_part_list.dart"; - /* * Widget for displaying detail view of a single Company instance */ class CompanyDetailWidget extends StatefulWidget { - const CompanyDetailWidget(this.company, {Key? key}) : super(key: key); final InvenTreeCompany company; @override _CompanyDetailState createState() => _CompanyDetailState(); - } - class _CompanyDetailState extends RefreshableState { - _CompanyDetailState(); int supplierPartCount = 0; @@ -59,17 +54,14 @@ class _CompanyDetailState extends RefreshableState { List actions = []; if (InvenTreeCompany().canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().companyEdit, - onPressed: () { - editCompany(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().companyEdit, + onPressed: () { + editCompany(context); + })); } - + return actions; } @@ -79,22 +71,20 @@ class _CompanyDetailState extends RefreshableState { if (widget.company.isCustomer && InvenTreeSalesOrder().canCreate) { actions.add(SpeedDialChild( - child: Icon(TablerIcons.truck), - label: L10().salesOrderCreate, - onTap: () async { - _createSalesOrder(context); - } - )); + child: Icon(TablerIcons.truck), + label: L10().salesOrderCreate, + onTap: () async { + _createSalesOrder(context); + })); } if (widget.company.isSupplier && InvenTreePurchaseOrder().canCreate) { actions.add(SpeedDialChild( - child: Icon(TablerIcons.shopping_cart), - label: L10().purchaseOrderCreate, - onTap: () async { - _createPurchaseOrder(context); - } - )); + child: Icon(TablerIcons.shopping_cart), + label: L10().purchaseOrderCreate, + onTap: () async { + _createPurchaseOrder(context); + })); } return actions; @@ -108,19 +98,15 @@ class _CompanyDetailState extends RefreshableState { fields["customer"]?["value"] = widget.company.pk; - InvenTreeSalesOrder().createForm( - context, - L10().salesOrderCreate, - fields: fields, - onSuccess: (result) async { - Map data = result as Map; + InvenTreeSalesOrder().createForm(context, L10().salesOrderCreate, + fields: fields, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var order = InvenTreeSalesOrder.fromJson(data); - order.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var order = InvenTreeSalesOrder.fromJson(data); + order.goToDetailPage(context); + } + }); } Future _createPurchaseOrder(BuildContext context) async { @@ -131,19 +117,15 @@ class _CompanyDetailState extends RefreshableState { fields["supplier"]?["value"] = widget.company.pk; - InvenTreePurchaseOrder().createForm( - context, - L10().purchaseOrderCreate, - fields: fields, - onSuccess: (result) async { - Map data = result as Map; + InvenTreePurchaseOrder().createForm(context, L10().purchaseOrderCreate, + fields: fields, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var order = InvenTreePurchaseOrder.fromJson(data); - order.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var order = InvenTreePurchaseOrder.fromJson(data); + order.goToDetailPage(context); + } + }); } @override @@ -156,23 +138,22 @@ class _CompanyDetailState extends RefreshableState { return; } - outstandingPurchaseOrders = widget.company.isSupplier ? - await InvenTreePurchaseOrder().count(filters: { - "supplier": widget.company.pk.toString(), - "outstanding": "true" - }) : 0; + outstandingPurchaseOrders = widget.company.isSupplier + ? await InvenTreePurchaseOrder().count(filters: { + "supplier": widget.company.pk.toString(), + "outstanding": "true" + }) + : 0; + + outstandingSalesOrders = widget.company.isCustomer + ? await InvenTreeSalesOrder().count(filters: { + "customer": widget.company.pk.toString(), + "outstanding": "true" + }) + : 0; - outstandingSalesOrders = widget.company.isCustomer ? - await InvenTreeSalesOrder().count(filters: { - "customer": widget.company.pk.toString(), - "outstanding": "true" - }) : 0; - InvenTreeSupplierPart().count( - filters: { - "supplier": widget.company.pk.toString() - } - ).then((value) { + filters: {"supplier": widget.company.pk.toString()}).then((value) { if (mounted) { setState(() { supplierPartCount = value; @@ -180,8 +161,9 @@ class _CompanyDetailState extends RefreshableState { } }); - InvenTreeCompanyAttachment().countAttachments(widget.company.pk) - .then((value) { + InvenTreeCompanyAttachment() + .countAttachments(widget.company.pk) + .then((value) { if (mounted) { setState(() { attachmentCount = value; @@ -190,16 +172,12 @@ class _CompanyDetailState extends RefreshableState { }); } - Future editCompany(BuildContext context) async { - - widget.company.editForm( - context, - L10().companyEdit, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().companyUpdated, success: true); - } - ); + Future editCompany(BuildContext context) async { + widget.company.editForm(context, L10().companyEdit, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().companyUpdated, success: true); + }); } /* @@ -207,7 +185,6 @@ class _CompanyDetailState extends RefreshableState { */ @override List getTiles(BuildContext context) { - List tiles = []; bool sep = false; @@ -221,63 +198,49 @@ class _CompanyDetailState extends RefreshableState { )); if (!widget.company.active) { - tiles.add( - ListTile( - title: Text( - L10().inactive, - style: TextStyle( - color: COLOR_DANGER - ) - ), - subtitle: Text( - L10().inactiveCompany, - style: TextStyle( - color: COLOR_DANGER - ) - ), - leading: Icon( - TablerIcons.exclamation_circle, - color: COLOR_DANGER - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().inactive, style: TextStyle(color: COLOR_DANGER)), + subtitle: + Text(L10().inactiveCompany, style: TextStyle(color: COLOR_DANGER)), + leading: Icon(TablerIcons.exclamation_circle, color: COLOR_DANGER), + )); } - if (widget.company.website.isNotEmpty) { - tiles.add(ListTile( - title: Text("${widget.company.website}"), - leading: Icon(TablerIcons.globe, color: COLOR_ACTION), - onTap: () async { - openLink(widget.company.website); - }, - )); + if (widget.company.website.isNotEmpty) { + tiles.add(ListTile( + title: Text("${widget.company.website}"), + leading: Icon(TablerIcons.globe, color: COLOR_ACTION), + onTap: () async { + openLink(widget.company.website); + }, + )); - sep = true; - } + sep = true; + } - if (widget.company.email.isNotEmpty) { - tiles.add(ListTile( - title: Text("${widget.company.email}"), - leading: Icon(TablerIcons.at, color: COLOR_ACTION), - onTap: () async { - openLink("mailto:${widget.company.email}"); - }, - )); + if (widget.company.email.isNotEmpty) { + tiles.add(ListTile( + title: Text("${widget.company.email}"), + leading: Icon(TablerIcons.at, color: COLOR_ACTION), + onTap: () async { + openLink("mailto:${widget.company.email}"); + }, + )); - sep = true; - } + sep = true; + } - if (widget.company.phone.isNotEmpty) { - tiles.add(ListTile( - title: Text("${widget.company.phone}"), - leading: Icon(TablerIcons.phone, color: COLOR_ACTION), - onTap: () { - openLink("tel:${widget.company.phone}"); - }, - )); + if (widget.company.phone.isNotEmpty) { + tiles.add(ListTile( + title: Text("${widget.company.phone}"), + leading: Icon(TablerIcons.phone, color: COLOR_ACTION), + onTap: () { + openLink("tel:${widget.company.phone}"); + }, + )); - sep = true; - } + sep = true; + } // External link if (widget.company.link.isNotEmpty) { @@ -297,46 +260,31 @@ class _CompanyDetailState extends RefreshableState { } if (widget.company.isSupplier) { - if (supplierPartCount > 0) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().supplierParts), leading: Icon(TablerIcons.building, color: COLOR_ACTION), trailing: Text(supplierPartCount.toString()), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SupplierPartList({ - "supplier": widget.company.pk.toString() - }) - ) - ); - } - ) - ); + context, + MaterialPageRoute( + builder: (context) => SupplierPartList( + {"supplier": widget.company.pk.toString()}))); + })); } - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().purchaseOrders), leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION), trailing: Text("${outstandingPurchaseOrders}"), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget( - filters: { - "supplier": "${widget.company.pk}" - } - ) - ) - ); - } - ) - ); + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget( + filters: {"supplier": "${widget.company.pk}"}))); + })); // TODO: Display "supplied parts" count (click through to list of supplier parts) /* @@ -355,25 +303,17 @@ class _CompanyDetailState extends RefreshableState { } if (widget.company.isCustomer) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().salesOrders), leading: Icon(TablerIcons.truck, color: COLOR_ACTION), trailing: Text("${outstandingSalesOrders}"), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SalesOrderListWidget( - filters: { - "customer": widget.company.pk.toString() - } - ) - ) - ); - } - ) - ); + context, + MaterialPageRoute( + builder: (context) => SalesOrderListWidget( + filters: {"customer": widget.company.pk.toString()}))); + })); } if (widget.company.notes.isNotEmpty) { @@ -384,27 +324,21 @@ class _CompanyDetailState extends RefreshableState { )); } - tiles.add(ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeCompanyAttachment(), - widget.company.pk, - widget.company.name, - InvenTreeCompany().canEdit - ) - ) - ); - } - )); + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget( + InvenTreeCompanyAttachment(), + widget.company.pk, + widget.company.name, + InvenTreeCompany().canEdit))); + })); return tiles; } - -} \ No newline at end of file +} diff --git a/lib/widget/company/company_list.dart b/lib/widget/company/company_list.dart index 8b0f03ca..06b92ade 100644 --- a/lib/widget/company/company_list.dart +++ b/lib/widget/company/company_list.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -12,13 +11,12 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; - /* * Widget for displaying a filterable list of Company instances */ class CompanyListWidget extends StatefulWidget { - - const CompanyListWidget(this.title, this.filters, {Key? key}) : super(key: key); + const CompanyListWidget(this.title, this.filters, {Key? key}) + : super(key: key); final String title; @@ -28,29 +26,22 @@ class CompanyListWidget extends StatefulWidget { _CompanyListWidgetState createState() => _CompanyListWidgetState(); } - class _CompanyListWidgetState extends RefreshableState { - _CompanyListWidgetState(); @override String getAppBarTitle() => widget.title; Future _addCompany(BuildContext context) async { + InvenTreeCompany().createForm(context, L10().companyAdd, + data: widget.filters, onSuccess: (result) async { + Map data = result as Map; - InvenTreeCompany().createForm( - context, - L10().companyAdd, - data: widget.filters, - onSuccess: (result) async { - Map data = result as Map; - - if (data.containsKey("pk")) { - var company = InvenTreeCompany.fromJson(data); - company.goToDetailPage(context); - } + if (data.containsKey("pk")) { + var company = InvenTreeCompany.fromJson(data); + company.goToDetailPage(context); } - ); + }); } @override @@ -58,15 +49,12 @@ class _CompanyListWidgetState extends RefreshableState { List actions = []; if (InvenTreeAPI().checkPermission("company", "add")) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_plus, color: Colors.green), - label: L10().companyAdd, - onTap: () { - _addCompany(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_plus, color: Colors.green), + label: L10().companyAdd, + onTap: () { + _addCompany(context); + })); } return actions; @@ -76,12 +64,11 @@ class _CompanyListWidgetState extends RefreshableState { Widget getBody(BuildContext context) { return PaginatedCompanyList(widget.title, widget.filters); } - } class PaginatedCompanyList extends PaginatedSearchWidget { - - const PaginatedCompanyList(this.companyTitle, Map filters) : super(filters: filters); + const PaginatedCompanyList(this.companyTitle, Map filters) + : super(filters: filters); final String companyTitle; @@ -93,12 +80,10 @@ class PaginatedCompanyList extends PaginatedSearchWidget { } class _CompanyListState extends PaginatedSearchState { - _CompanyListState() : super(); @override Map> get filterOptions { - Map> filters = {}; if (InvenTreeAPI().supportsCompanyActiveStatus) { @@ -113,16 +98,16 @@ class _CompanyListState extends PaginatedSearchState { } @override - Future requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreeCompany().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = + await InvenTreeCompany().listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeCompany company = model as InvenTreeCompany; return ListTile( diff --git a/lib/widget/company/manufacturer_part_detail.dart b/lib/widget/company/manufacturer_part_detail.dart index 6d8fd095..49f21528 100644 --- a/lib/widget/company/manufacturer_part_detail.dart +++ b/lib/widget/company/manufacturer_part_detail.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -19,19 +18,18 @@ import "package:url_launcher/url_launcher.dart"; * Detail widget for viewing a single ManufacturerPart instance */ class ManufacturerPartDetailWidget extends StatefulWidget { - const ManufacturerPartDetailWidget(this.manufacturerPart, {Key? key}) : super(key: key); final InvenTreeManufacturerPart manufacturerPart; @override - _ManufacturerPartDisplayState createState() => _ManufacturerPartDisplayState(); + _ManufacturerPartDisplayState createState() => + _ManufacturerPartDisplayState(); } - -class _ManufacturerPartDisplayState extends RefreshableState { - +class _ManufacturerPartDisplayState + extends RefreshableState { _ManufacturerPartDisplayState(); @override @@ -48,14 +46,11 @@ class _ManufacturerPartDisplayState extends RefreshableState editManufacturerPart(BuildContext context) async { - widget.manufacturerPart.editForm( - context, - L10().manufacturerPartEdit, + widget.manufacturerPart.editForm(context, L10().manufacturerPartEdit, onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().itemUpdated, success: true); - } - ); + refresh(context); + showSnackIcon(L10().itemUpdated, success: true); + }); } @override @@ -72,15 +67,12 @@ class _ManufacturerPartDisplayState extends RefreshableState actions = []; if (widget.manufacturerPart.canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().edit, - onPressed: () { - editManufacturerPart(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().edit, + onPressed: () { + editManufacturerPart(context); + })); } return actions; @@ -99,79 +91,69 @@ class _ManufacturerPartDisplayState extends RefreshableState _SupplierPartDisplayState(); } - -class _SupplierPartDisplayState extends RefreshableState { - +class _SupplierPartDisplayState + extends RefreshableState { _SupplierPartDisplayState(); @override @@ -43,14 +41,11 @@ class _SupplierPartDisplayState extends RefreshableState editSupplierPart(BuildContext context) async { - widget.supplierPart.editForm( - context, - L10().supplierPartEdit, + widget.supplierPart.editForm(context, L10().supplierPartEdit, onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().supplierPartUpdated, success: true); - } - ); + refresh(context); + showSnackIcon(L10().supplierPartUpdated, success: true); + }); } @override @@ -58,14 +53,12 @@ class _SupplierPartDisplayState extends RefreshableState actions = []; if (widget.supplierPart.canEdit) { - actions.add( - customBarcodeAction( - context, this, + actions.add(customBarcodeAction( + context, + this, widget.supplierPart.customBarcode, "supplierpart", - widget.supplierPart.pk - ) - ); + widget.supplierPart.pk)); } return actions; @@ -76,15 +69,12 @@ class _SupplierPartDisplayState extends RefreshableState actions = []; if (widget.supplierPart.canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().edit, - onPressed: () { - editSupplierPart(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().edit, + onPressed: () { + editSupplierPart(context); + })); } return actions; @@ -92,7 +82,8 @@ class _SupplierPartDisplayState extends RefreshableState request(BuildContext context) async { - final bool result = widget.supplierPart.pk > 0 && await widget.supplierPart.reload(); + final bool result = + widget.supplierPart.pk > 0 && await widget.supplierPart.reload(); if (!result) { Navigator.of(context).pop(); @@ -112,152 +103,131 @@ class _SupplierPartDisplayState extends RefreshableState 0) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().manufacturer), subtitle: Text(widget.supplierPart.manufacturerName), leading: Icon(TablerIcons.building_factory_2, color: COLOR_ACTION), - trailing: InvenTreeAPI().getThumbnail(widget.supplierPart.manufacturerImage), + trailing: InvenTreeAPI() + .getThumbnail(widget.supplierPart.manufacturerImage), onTap: () async { showLoadingOverlay(); - var supplier = await InvenTreeCompany().get(widget.supplierPart.manufacturerId); + var supplier = await InvenTreeCompany() + .get(widget.supplierPart.manufacturerId); hideLoadingOverlay(); if (supplier is InvenTreeCompany) { supplier.goToDetailPage(context); } + })); + + tiles.add(ListTile( + title: Text(L10().manufacturerPartNumber), + subtitle: Text(widget.supplierPart.MPN), + leading: Icon(TablerIcons.hash, color: COLOR_ACTION), + onTap: () async { + showLoadingOverlay(); + var manufacturerPart = await InvenTreeManufacturerPart() + .get(widget.supplierPart.manufacturerPartId); + hideLoadingOverlay(); + + if (manufacturerPart is InvenTreeManufacturerPart) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + ManufacturerPartDetailWidget(manufacturerPart))); } - ) - ); - - tiles.add( - ListTile( - title: Text(L10().manufacturerPartNumber), - subtitle: Text(widget.supplierPart.MPN), - leading: Icon(TablerIcons.hash, color: COLOR_ACTION), - onTap: () async { - showLoadingOverlay(); - var manufacturerPart = await InvenTreeManufacturerPart().get(widget.supplierPart.manufacturerPartId); - hideLoadingOverlay(); - - if (manufacturerPart is InvenTreeManufacturerPart) { - Navigator.push(context, MaterialPageRoute( - builder: (context) => ManufacturerPartDetailWidget(manufacturerPart) - )); - } - }, - ) - ); + }, + )); } // Packaging - if (widget.supplierPart.packaging.isNotEmpty || widget.supplierPart.pack_quantity.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().packaging), - subtitle: widget.supplierPart.packaging.isNotEmpty ? Text(widget.supplierPart.packaging) : null, - leading: Icon(TablerIcons.package), - trailing: widget.supplierPart.pack_quantity.isNotEmpty ? Text(widget.supplierPart.pack_quantity) : null, - ) - ); + if (widget.supplierPart.packaging.isNotEmpty || + widget.supplierPart.pack_quantity.isNotEmpty) { + tiles.add(ListTile( + title: Text(L10().packaging), + subtitle: widget.supplierPart.packaging.isNotEmpty + ? Text(widget.supplierPart.packaging) + : null, + leading: Icon(TablerIcons.package), + trailing: widget.supplierPart.pack_quantity.isNotEmpty + ? Text(widget.supplierPart.pack_quantity) + : null, + )); } if (widget.supplierPart.link.isNotEmpty) { - tiles.add( - ListTile( - title: Text(widget.supplierPart.link), - leading: Icon(TablerIcons.link, color: COLOR_ACTION), - onTap: () async { - var uri = Uri.tryParse(widget.supplierPart.link); - if (uri != null && await canLaunchUrl(uri)) { - await launchUrl(uri); - } - }, - ) - ); + tiles.add(ListTile( + title: Text(widget.supplierPart.link), + leading: Icon(TablerIcons.link, color: COLOR_ACTION), + onTap: () async { + var uri = Uri.tryParse(widget.supplierPart.link); + if (uri != null && await canLaunchUrl(uri)) { + await launchUrl(uri); + } + }, + )); } if (widget.supplierPart.note.isNotEmpty) { - tiles.add( - ListTile( - title: Text(widget.supplierPart.note), - leading: Icon(TablerIcons.pencil), - ) - ); + tiles.add(ListTile( + title: Text(widget.supplierPart.note), + leading: Icon(TablerIcons.pencil), + )); } return tiles; } - -} \ No newline at end of file +} diff --git a/lib/widget/company/supplier_part_list.dart b/lib/widget/company/supplier_part_list.dart index ce8fe531..cb6d62b2 100644 --- a/lib/widget/company/supplier_part_list.dart +++ b/lib/widget/company/supplier_part_list.dart @@ -10,12 +10,10 @@ import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/company/supplier_part_detail.dart"; - /* * Widget for displaying a list of Supplier Part instances */ class SupplierPartList extends StatefulWidget { - const SupplierPartList(this.filters); final Map filters; @@ -24,9 +22,7 @@ class SupplierPartList extends StatefulWidget { _SupplierPartListState createState() => _SupplierPartListState(); } - class _SupplierPartListState extends RefreshableState { - @override String getAppBarTitle() => L10().supplierParts; @@ -34,25 +30,22 @@ class _SupplierPartListState extends RefreshableState { Widget getBody(BuildContext context) { return PaginatedSupplierPartList(widget.filters); } - } - class PaginatedSupplierPartList extends PaginatedSearchWidget { - - const PaginatedSupplierPartList(Map filters) : super(filters: filters); + const PaginatedSupplierPartList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().supplierParts; @override - _PaginatedSupplierPartListState createState() => _PaginatedSupplierPartListState(); - + _PaginatedSupplierPartListState createState() => + _PaginatedSupplierPartListState(); } - -class _PaginatedSupplierPartListState extends PaginatedSearchState { - +class _PaginatedSupplierPartListState + extends PaginatedSearchState { _PaginatedSupplierPartListState() : super(); @override @@ -63,7 +56,6 @@ class _PaginatedSupplierPartListState extends PaginatedSearchState> get filterOptions { - Map> filters = {}; if (InvenTreeAPI().supportsCompanyActiveStatus) { @@ -78,8 +70,10 @@ class _PaginatedSupplierPartListState extends PaginatedSearchState requestPage(int limit, int offset, Map params) async { - final page = await InvenTreeSupplierPart().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreeSupplierPart() + .listPaginated(limit, offset, filters: params); return page; } @@ -94,12 +88,10 @@ class _PaginatedSupplierPartListState extends PaginatedSearchState SupplierPartDetailWidget(supplierPart) - ) - ); + context, + MaterialPageRoute( + builder: (context) => SupplierPartDetailWidget(supplierPart))); }, ); } -} \ No newline at end of file +} diff --git a/lib/widget/dialogs.dart b/lib/widget/dialogs.dart index 90b0e290..b64cad5e 100644 --- a/lib/widget/dialogs.dart +++ b/lib/widget/dialogs.dart @@ -9,61 +9,58 @@ import "package:inventree/l10.dart"; import "package:inventree/preferences.dart"; import "package:inventree/widget/snacks.dart"; - /* * Launch a dialog allowing the user to select from a list of options */ -Future choiceDialog(String title, List items, {Function? onSelected}) async { - +Future choiceDialog(String title, List items, + {Function? onSelected}) async { List choices = []; for (int idx = 0; idx < items.length; idx++) { - choices.add( - GestureDetector( - child: items[idx], - onTap: () { - OneContext().popDialog(); - if (onSelected != null) { - onSelected(idx); - } - }, - ) - ); + choices.add(GestureDetector( + child: items[idx], + onTap: () { + OneContext().popDialog(); + if (onSelected != null) { + onSelected(idx); + } + }, + )); } if (!hasContext()) { return; } - OneContext().showDialog( - builder: (BuildContext context) { - return AlertDialog( - title: Text(title), - content: SingleChildScrollView( + OneContext().showDialog(builder: (BuildContext context) { + return AlertDialog( + title: Text(title), + content: SingleChildScrollView( child: Column( - children: choices, - ) - ), - actions: [ - TextButton( - child: Text(L10().cancel), - onPressed: () { - Navigator.pop(context); - }, - ) - ], - ); - } - ); - + children: choices, + )), + actions: [ + TextButton( + child: Text(L10().cancel), + onPressed: () { + Navigator.pop(context); + }, + ) + ], + ); + }); } - /* * Display a "confirmation" dialog allowing the user to accept or reject an action */ -Future confirmationDialog(String title, String text, {Color? color, IconData icon = TablerIcons.help_circle, String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async { - +Future confirmationDialog(String title, String text, + {Color? color, + IconData icon = TablerIcons.help_circle, + String? acceptText, + String? rejectText, + Function? onAccept, + Function? onReject}) async { String _accept = acceptText ?? L10().ok; String _reject = rejectText ?? L10().cancel; @@ -71,9 +68,8 @@ Future confirmationDialog(String title, String text, {Color? color, IconDa return; } - OneContext().showDialog( - builder: (BuildContext context) { - return AlertDialog( + OneContext().showDialog(builder: (BuildContext context) { + return AlertDialog( iconColor: color, title: ListTile( title: Text(title, style: TextStyle(color: color)), @@ -82,16 +78,15 @@ Future confirmationDialog(String title, String text, {Color? color, IconDa content: text.isEmpty ? Text(text) : null, actions: [ TextButton( - child: Text(_reject), - onPressed: () { - // Close this dialog - Navigator.pop(context); + child: Text(_reject), + onPressed: () { + // Close this dialog + Navigator.pop(context); - if (onReject != null) { - onReject(); - } - } - ), + if (onReject != null) { + onReject(); + } + }), TextButton( child: Text(_accept), onPressed: () { @@ -103,13 +98,10 @@ Future confirmationDialog(String title, String text, {Color? color, IconDa } }, ) - ] - ); - } - ); + ]); + }); } - /* * Construct an error dialog showing information to the user * @@ -117,68 +109,56 @@ Future confirmationDialog(String title, String text, {Color? color, IconDa * @description = Simple string description of error * @data = Error response (e.g from server) */ -Future showErrorDialog(String title, {String description = "", APIResponse? response, IconData icon = TablerIcons.exclamation_circle, Function? onDismissed}) async { - +Future showErrorDialog(String title, + {String description = "", + APIResponse? response, + IconData icon = TablerIcons.exclamation_circle, + Function? onDismissed}) async { List children = []; if (description.isNotEmpty) { - children.add( - ListTile( - title: Text(description), - ) - ); + children.add(ListTile( + title: Text(description), + )); } else if (response != null) { // Look for extra error information in the provided APIResponse object switch (response.statusCode) { - case 400: // Bad request (typically bad input) + case 400: // Bad request (typically bad input) if (response.data is Map) { - for (String field in response.asMap().keys) { - dynamic error = response.data[field]; if (error is List) { for (int ii = 0; ii < error.length; ii++) { - children.add( - ListTile( - title: Text(field), - subtitle: Text(error[ii].toString()), - ) - ); + children.add(ListTile( + title: Text(field), + subtitle: Text(error[ii].toString()), + )); } } else { - children.add( - ListTile( - title: Text(field), - subtitle: Text(response.data[field].toString()), - ) - ); + children.add(ListTile( + title: Text(field), + subtitle: Text(response.data[field].toString()), + )); } } } else { - children.add( - ListTile( + children.add(ListTile( title: Text(L10().responseInvalid), - subtitle: Text(response.data.toString()) - ) - ); + subtitle: Text(response.data.toString()))); } break; default: // Unhandled server response - children.add( - ListTile( - title: Text(L10().statusCode), - subtitle: Text(response.statusCode.toString()), - ) - ); + children.add(ListTile( + title: Text(L10().statusCode), + subtitle: Text(response.statusCode.toString()), + )); - children.add( - ListTile( - title: Text(L10().responseData), - subtitle: Text(response.data.toString()), - ) - ); + children.add(ListTile( + title: Text(L10().responseData), + subtitle: Text(response.data.toString()), + )); break; } @@ -188,15 +168,15 @@ Future showErrorDialog(String title, {String description = "", APIResponse return; } - OneContext().showDialog( - builder: (context) => SimpleDialog( - title: ListTile( - title: Text(title), - leading: Icon(icon), - ), - children: children - ) - ).then((value) { + OneContext() + .showDialog( + builder: (context) => SimpleDialog( + title: ListTile( + title: Text(title), + leading: Icon(icon), + ), + children: children)) + .then((value) { if (onDismissed != null) { onDismissed(); } @@ -206,8 +186,8 @@ Future showErrorDialog(String title, {String description = "", APIResponse /* * Display a message indicating the nature of a server / API error */ -Future showServerError(String url, String title, String description) async { - +Future showServerError( + String url, String title, String description) async { if (!hasContext()) { return; } @@ -222,7 +202,8 @@ Future showServerError(String url, String title, String description) async } // Play a sound - final bool tones = await InvenTreeSettingsManager().getValue(INV_SOUNDS_SERVER, true) as bool; + final bool tones = await InvenTreeSettingsManager() + .getValue(INV_SOUNDS_SERVER, true) as bool; if (tones) { playAudioFile("sounds/server_error.mp3"); @@ -230,25 +211,16 @@ Future showServerError(String url, String title, String description) async description += "\nURL: $url"; - showSnackIcon( - title, - success: false, - actionText: L10().details, - onAction: () { - showErrorDialog( - title, - description: description, - icon: TablerIcons.server - ); - } - ); + showSnackIcon(title, success: false, actionText: L10().details, onAction: () { + showErrorDialog(title, description: description, icon: TablerIcons.server); + }); } /* * Displays an error indicating that the server returned an unexpected status code */ -Future showStatusCodeError(String url, int status, {String details=""}) async { - +Future showStatusCodeError(String url, int status, + {String details = ""}) async { String msg = statusCodeToString(status); String extra = url + "\n" + "${L10().statusCode}: ${status}"; @@ -264,7 +236,6 @@ Future showStatusCodeError(String url, int status, {String details=""}) as ); } - /* * Provide a human-readable descriptor for a particular error code */ @@ -299,7 +270,6 @@ String statusCodeToString(int status) { } } - /* * Displays a message indicating that the server timed out on a certain request */ diff --git a/lib/widget/drawer.dart b/lib/widget/drawer.dart index 2288e340..7541dff5 100644 --- a/lib/widget/drawer.dart +++ b/lib/widget/drawer.dart @@ -16,12 +16,10 @@ import "package:inventree/widget/notifications.dart"; import "package:inventree/widget/order/purchase_order_list.dart"; import "package:inventree/widget/stock/location_display.dart"; - /* * Custom "drawer" widget for the InvenTree app. */ class InvenTreeDrawer extends StatelessWidget { - const InvenTreeDrawer(this.context); final BuildContext context; @@ -52,10 +50,8 @@ class InvenTreeDrawer extends StatelessWidget { _closeDrawer(); if (_checkConnection()) { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); } } @@ -64,10 +60,8 @@ class InvenTreeDrawer extends StatelessWidget { _closeDrawer(); if (_checkConnection()) { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } } @@ -79,12 +73,10 @@ class InvenTreeDrawer extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => SalesOrderListWidget(filters: {}) - ) - ); + builder: (context) => SalesOrderListWidget(filters: {}))); } } - + // Load "purchase orders" page void _purchaseOrders() { _closeDrawer(); @@ -93,9 +85,7 @@ class InvenTreeDrawer extends StatelessWidget { Navigator.push( context, MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget(filters: {}) - ) - ); + builder: (context) => PurchaseOrderListWidget(filters: {}))); } } @@ -112,7 +102,8 @@ class InvenTreeDrawer extends StatelessWidget { // Load settings widget void _settings() { _closeDrawer(); - Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget())); + Navigator.push(context, + MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget())); } // Construct list of tiles to display in the "drawer" menu @@ -132,43 +123,35 @@ class InvenTreeDrawer extends StatelessWidget { tiles.add(Divider()); if (InvenTreePart().canView) { - tiles.add( - ListTile( - title: Text(L10().parts), - leading: Icon(TablerIcons.box, color: COLOR_ACTION), - onTap: _parts, - ) - ); + tiles.add(ListTile( + title: Text(L10().parts), + leading: Icon(TablerIcons.box, color: COLOR_ACTION), + onTap: _parts, + )); } if (InvenTreeStockLocation().canView) { - tiles.add( - ListTile( - title: Text(L10().stock), - leading: Icon(TablerIcons.package, color: COLOR_ACTION), - onTap: _stock, - ) - ); + tiles.add(ListTile( + title: Text(L10().stock), + leading: Icon(TablerIcons.package, color: COLOR_ACTION), + onTap: _stock, + )); } if (InvenTreePurchaseOrder().canView) { - tiles.add( - ListTile( - title: Text(L10().purchaseOrders), - leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION), - onTap: _purchaseOrders, - ) - ); + tiles.add(ListTile( + title: Text(L10().purchaseOrders), + leading: Icon(TablerIcons.shopping_cart, color: COLOR_ACTION), + onTap: _purchaseOrders, + )); } if (InvenTreeSalesOrder().canView) { - tiles.add( - ListTile( - title: Text(L10().salesOrders), - leading: Icon(TablerIcons.truck_delivery, color: COLOR_ACTION), - onTap: _salesOrders, - ) - ); + tiles.add(ListTile( + title: Text(L10().salesOrders), + leading: Icon(TablerIcons.truck_delivery, color: COLOR_ACTION), + onTap: _salesOrders, + )); } if (tiles.length > 2) { @@ -177,55 +160,44 @@ class InvenTreeDrawer extends StatelessWidget { int notification_count = InvenTreeAPI().notification_counter; - tiles.add( - ListTile( - leading: Icon(TablerIcons.bell, color: COLOR_ACTION), - trailing: notification_count > 0 ? Text(notification_count.toString()) : null, - title: Text(L10().notifications), - onTap: _notifications, - ) - ); + tiles.add(ListTile( + leading: Icon(TablerIcons.bell, color: COLOR_ACTION), + trailing: + notification_count > 0 ? Text(notification_count.toString()) : null, + title: Text(L10().notifications), + onTap: _notifications, + )); tiles.add(Divider()); bool darkMode = AdaptiveTheme.of(context).mode.isDark; - tiles.add( - ListTile( + tiles.add(ListTile( onTap: () { AdaptiveTheme.of(context).toggleThemeMode(); _closeDrawer(); }, title: Text(L10().colorScheme), subtitle: Text(L10().colorSchemeDetail), - leading: Icon( - TablerIcons.sun_moon, - color: COLOR_ACTION - ), + leading: Icon(TablerIcons.sun_moon, color: COLOR_ACTION), trailing: Icon( darkMode ? TablerIcons.moon : TablerIcons.sun, - ) - ) - ); + ))); - tiles.add( - ListTile( - title: Text(L10().settings), - leading: Icon(Icons.settings, color: COLOR_ACTION), - onTap: _settings, - ) - ); + tiles.add(ListTile( + title: Text(L10().settings), + leading: Icon(Icons.settings, color: COLOR_ACTION), + onTap: _settings, + )); return tiles; } @override Widget build(BuildContext context) { - - return Drawer( + return Drawer( child: ListView( - children: drawerTiles(context), - ) - ); + children: drawerTiles(context), + )); } } diff --git a/lib/widget/fields.dart b/lib/widget/fields.dart index 8a7d6007..80dc636d 100644 --- a/lib/widget/fields.dart +++ b/lib/widget/fields.dart @@ -9,11 +9,8 @@ import "package:one_context/one_context.dart"; import "package:inventree/l10.dart"; - class FilePickerDialog { - static Future pickImageFromCamera() async { - final picker = ImagePicker(); final pickedImage = await picker.pickImage(source: ImageSource.camera); @@ -26,7 +23,6 @@ class FilePickerDialog { } static Future pickImageFromGallery() async { - final picker = ImagePicker(); final pickedImage = await picker.pickImage(source: ImageSource.gallery); @@ -39,7 +35,6 @@ class FilePickerDialog { } static Future pickFileFromDevice() async { - final FilePickerResult? result = await FilePicker.platform.pickFiles(); if (result != null) { @@ -54,8 +49,11 @@ class FilePickerDialog { } // Present a dialog to pick a file, either from local file system or from camera - static Future pickFile({String message = "", bool allowImages = true, bool allowFiles = true, Function(File)? onPicked}) async { - + static Future pickFile( + {String message = "", + bool allowImages = true, + bool allowFiles = true, + Function(File)? onPicked}) async { String title = ""; if (allowImages && !allowFiles) { @@ -65,48 +63,38 @@ class FilePickerDialog { } // Construct actions - List actions = [ - - ]; + List actions = []; if (message.isNotEmpty) { - actions.add( - ListTile( - title: Text(message) - ) - ); + actions.add(ListTile(title: Text(message))); } - actions.add( - SimpleDialogOption( - child: ListTile( - leading: Icon(TablerIcons.arrow_up), - title: Text(allowFiles ? L10().selectFile : L10().selectImage), - ), - onPressed: () async { + actions.add(SimpleDialogOption( + child: ListTile( + leading: Icon(TablerIcons.arrow_up), + title: Text(allowFiles ? L10().selectFile : L10().selectImage), + ), + onPressed: () async { + // Close the dialog + OneContext().popDialog(); - // Close the dialog - OneContext().popDialog(); + File? file; + if (allowFiles) { + file = await pickFileFromDevice(); + } else { + file = await pickImageFromGallery(); + } - File? file; - if (allowFiles) { - file = await pickFileFromDevice(); - } else { - file = await pickImageFromGallery(); + if (file != null) { + if (onPicked != null) { + onPicked(file); } - - if (file != null) { - if (onPicked != null) { - onPicked(file); - } - } - }, - ) - ); + } + }, + )); if (allowImages) { - actions.add( - SimpleDialogOption( + actions.add(SimpleDialogOption( child: ListTile( leading: Icon(TablerIcons.camera), title: Text(L10().takePicture), @@ -122,100 +110,99 @@ class FilePickerDialog { onPicked(file); } } - } - ) - ); + })); } - OneContext().showDialog( - builder: (context) { - return SimpleDialog( - title: Text(title), - children: actions, - ); - } - ); + OneContext().showDialog(builder: (context) { + return SimpleDialog( + title: Text(title), + children: actions, + ); + }); } - } - class CheckBoxField extends FormField { CheckBoxField({ - String? label, - bool? initial = false, - bool tristate = false, - Function(bool?)? onSaved, - TextStyle? labelStyle, - String? helperText, - TextStyle? helperStyle, - }) : - super( - onSaved: onSaved, - initialValue: initial, - builder: (FormFieldState state) { - - return CheckboxListTile( - title: label != null ? Text(label, style: labelStyle) : null, - value: state.value, - tristate: tristate, - onChanged: state.didChange, - subtitle: helperText != null ? Text(helperText, style: helperStyle) : null, - contentPadding: EdgeInsets.zero, - ); - } - ); + String? label, + bool? initial = false, + bool tristate = false, + Function(bool?)? onSaved, + TextStyle? labelStyle, + String? helperText, + TextStyle? helperStyle, + }) : super( + onSaved: onSaved, + initialValue: initial, + builder: (FormFieldState state) { + return CheckboxListTile( + title: label != null ? Text(label, style: labelStyle) : null, + value: state.value, + tristate: tristate, + onChanged: state.didChange, + subtitle: helperText != null + ? Text(helperText, style: helperStyle) + : null, + contentPadding: EdgeInsets.zero, + ); + }); } class StringField extends TextFormField { + StringField( + {String label = "", + String? hint, + String? initial, + Function(String?)? onSaved, + Function(String?)? validator, + bool allowEmpty = false, + bool isEnabled = true}) + : super( + decoration: InputDecoration( + labelText: allowEmpty ? label : label + "*", hintText: hint), + initialValue: initial, + onSaved: onSaved, + enabled: isEnabled, + validator: (value) { + if (!allowEmpty && value != null && value.isEmpty) { + return L10().valueCannotBeEmpty; + } - StringField({String label = "", String? hint, String? initial, Function(String?)? onSaved, Function(String?)? validator, bool allowEmpty = false, bool isEnabled = true}) : - super( - decoration: InputDecoration( - labelText: allowEmpty ? label : label + "*", - hintText: hint - ), - initialValue: initial, - onSaved: onSaved, - enabled: isEnabled, - validator: (value) { - if (!allowEmpty && value != null && value.isEmpty) { - return L10().valueCannotBeEmpty; - } + if (validator != null) { + return validator(value) as String?; + } - if (validator != null) { - return validator(value) as String?; - } - - return null; - } - ); + return null; + }); } - /* * Helper class for quantity values */ class QuantityField extends TextFormField { + QuantityField( + {String label = "", + String hint = "", + double? max, + TextEditingController? controller}) + : super( + decoration: InputDecoration( + labelText: label, + hintText: hint, + ), + controller: controller, + keyboardType: + TextInputType.numberWithOptions(signed: false, decimal: true), + validator: (value) { + if (value != null && value.isEmpty) return L10().quantityEmpty; - QuantityField({String label = "", String hint = "", double? max, TextEditingController? controller}) : - super( - decoration: InputDecoration( - labelText: label, - hintText: hint, - ), - controller: controller, - keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true), - validator: (value) { + double quantity = double.tryParse(value.toString()) ?? 0; - if (value != null && value.isEmpty) return L10().quantityEmpty; + if (quantity <= 0) return L10().quantityPositive; + if ((max != null) && (quantity > max)) + return "Quantity must not exceed ${max}"; - double quantity = double.tryParse(value.toString()) ?? 0; - - if (quantity <= 0) return L10().quantityPositive; - if ((max != null) && (quantity > max)) return "Quantity must not exceed ${max}"; - - return null; - }, - ); -} \ No newline at end of file + return null; + }, + ); +} diff --git a/lib/widget/home.dart b/lib/widget/home.dart index f7a84045..a392ec7d 100644 --- a/lib/widget/home.dart +++ b/lib/widget/home.dart @@ -26,18 +26,15 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/spinner.dart"; import "package:inventree/widget/company/company_list.dart"; - class InvenTreeHomePage extends StatefulWidget { - const InvenTreeHomePage({Key? key}) : super(key: key); @override _InvenTreeHomePageState createState() => _InvenTreeHomePageState(); } - -class _InvenTreeHomePageState extends State with BaseWidgetProperties { - +class _InvenTreeHomePageState extends State + with BaseWidgetProperties { _InvenTreeHomePageState() : super() { // Load display settings _loadSettings(); @@ -46,7 +43,6 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr _loadProfile(); InvenTreeAPI().registerCallback(() { - if (mounted) { setState(() { // Reload the widget @@ -70,37 +66,31 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr void _showParts(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; - Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); + Navigator.push(context, + MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); } void _showStarredParts(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PartList({ - "starred": "true" - }) - ) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => PartList({"starred": "true"}))); } void _showStock(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; - Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); + Navigator.push(context, + MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))); } void _showPurchaseOrders(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget(filters: {}) - ) - ); + context, + MaterialPageRoute( + builder: (context) => PurchaseOrderListWidget(filters: {}))); } void _showSalesOrders(BuildContext context) { @@ -109,15 +99,17 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr Navigator.push( context, MaterialPageRoute( - builder: (context) => SalesOrderListWidget(filters: {}) - ) - ); + builder: (context) => SalesOrderListWidget(filters: {}))); } void _showSuppliers(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; - Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().suppliers, {"is_supplier": "true"}))); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + CompanyListWidget(L10().suppliers, {"is_supplier": "true"}))); } /* @@ -131,39 +123,47 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr void _showCustomers(BuildContext context) { if (!InvenTreeAPI().checkConnection()) return; - Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyListWidget(L10().customers, {"is_customer": "true"}))); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + CompanyListWidget(L10().customers, {"is_customer": "true"}))); } void _selectProfile() { Navigator.push( - context, MaterialPageRoute(builder: (context) => InvenTreeSelectServerWidget()) - ).then((context) { + context, + MaterialPageRoute( + builder: (context) => InvenTreeSelectServerWidget())) + .then((context) { // Once we return _loadProfile(); }); } - Future _loadSettings() async { + Future _loadSettings() async { + homeShowSubscribed = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; + homeShowPo = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_PO, true) as bool; + homeShowSo = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SO, true) as bool; + homeShowManufacturers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; + homeShowCustomers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; + homeShowSuppliers = await InvenTreeSettingsManager() + .getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; - homeShowSubscribed = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUBSCRIBED, true) as bool; - homeShowPo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_PO, true) as bool; - homeShowSo = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SO, true) as bool; - homeShowManufacturers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_MANUFACTURERS, true) as bool; - homeShowCustomers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_CUSTOMERS, true) as bool; - homeShowSuppliers = await InvenTreeSettingsManager().getValue(INV_HOME_SHOW_SUPPLIERS, true) as bool; - - setState(() { - }); + setState(() {}); } - Future _loadProfile() async { - + Future _loadProfile() async { _profile = await UserProfileDBManager().getSelectedProfile(); // A valid profile was loaded! if (_profile != null) { if (!InvenTreeAPI().isConnected() && !InvenTreeAPI().isConnecting()) { - // Attempt server connection InvenTreeAPI().connectToServer(_profile!).then((result) { if (mounted) { @@ -176,8 +176,11 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr setState(() {}); } - Widget _listTile(BuildContext context, String label, IconData icon, {Function()? callback, String role = "", String permission = "", Widget? trailing}) { - + Widget _listTile(BuildContext context, String label, IconData icon, + {Function()? callback, + String role = "", + String permission = "", + Widget? trailing}) { bool connected = InvenTreeAPI().isConnected(); bool allowed = true; @@ -188,25 +191,20 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr return GestureDetector( child: Card( - margin: EdgeInsets.all(5), - child: Align( - child: ListTile( - leading: Icon( - icon, - size: 32, - color: connected && allowed ? COLOR_ACTION : Colors.grey - ), - title: Text( - label, - style: TextStyle( - fontSize: 20 + margin: EdgeInsets.all(5), + child: Align( + child: ListTile( + leading: Icon(icon, + size: 32, + color: connected && allowed ? COLOR_ACTION : Colors.grey), + title: Text( + label, + style: TextStyle(fontSize: 20), ), + trailing: trailing, ), - trailing: trailing, - ), - alignment: Alignment.center, - ) - ), + alignment: Alignment.center, + )), onTap: () { if (!allowed) { showSnackIcon( @@ -228,7 +226,6 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr * Constructs a list of tiles for the main screen */ List getListTiles(BuildContext context) { - List tiles = []; // Parts @@ -245,61 +242,42 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr // Starred parts if (homeShowSubscribed && InvenTreePart().canView) { - tiles.add(_listTile( - context, - L10().partsStarred, - TablerIcons.bell, - callback: () { - _showStarredParts(context); - } - )); + tiles.add(_listTile(context, L10().partsStarred, TablerIcons.bell, + callback: () { + _showStarredParts(context); + })); } // Stock button if (InvenTreeStockItem().canView) { - tiles.add(_listTile( - context, - L10().stock, - TablerIcons.package, - callback: () { - _showStock(context); - } - )); + tiles.add( + _listTile(context, L10().stock, TablerIcons.package, callback: () { + _showStock(context); + })); } // Purchase orders if (homeShowPo && InvenTreePurchaseOrder().canView) { - tiles.add(_listTile( - context, - L10().purchaseOrders, - TablerIcons.shopping_cart, - callback: () { - _showPurchaseOrders(context); - } - )); + tiles.add( + _listTile(context, L10().purchaseOrders, TablerIcons.shopping_cart, + callback: () { + _showPurchaseOrders(context); + })); } if (homeShowSo && InvenTreeSalesOrder().canView) { tiles.add(_listTile( - context, - L10().salesOrders, - TablerIcons.truck_delivery, - callback: () { - _showSalesOrders(context); - } - )); + context, L10().salesOrders, TablerIcons.truck_delivery, callback: () { + _showSalesOrders(context); + })); } // Suppliers if (homeShowSuppliers && InvenTreePurchaseOrder().canView) { - tiles.add(_listTile( - context, - L10().suppliers, - TablerIcons.building, + tiles.add(_listTile(context, L10().suppliers, TablerIcons.building, callback: () { - _showSuppliers(context); - } - )); + _showSuppliers(context); + })); } // TODO: Add these tiles back in once the features are fleshed out @@ -320,14 +298,10 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr */ // Customers if (homeShowCustomers) { - tiles.add(_listTile( - context, - L10().customers, - TablerIcons.building_store, + tiles.add(_listTile(context, L10().customers, TablerIcons.building_store, callback: () { - _showCustomers(context); - } - )); + _showCustomers(context); + })); } return tiles; @@ -338,10 +312,10 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr * display a connection status widget */ Widget _connectionStatusWidget(BuildContext context) { - String? serverAddress = InvenTreeAPI().serverAddress; bool validAddress = serverAddress != null; - bool connecting = !InvenTreeAPI().isConnected() && InvenTreeAPI().isConnecting(); + bool connecting = + !InvenTreeAPI().isConnected() && InvenTreeAPI().isConnecting(); Widget leading = Icon(TablerIcons.exclamation_circle, color: COLOR_DANGER); Widget trailing = Icon(TablerIcons.server, color: COLOR_ACTION); @@ -357,25 +331,23 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr } return Center( - child: Column( - children: [ - Spacer(), - Image.asset( - "assets/image/logo_transparent.png", - color: Colors.white.withValues(alpha: 0.05), - colorBlendMode: BlendMode.modulate, - scale: 0.5, - ), - Spacer(), - ListTile( - title: Text(title), - subtitle: Text(subtitle), - trailing: trailing, - leading: leading, - onTap: _selectProfile, - ) - ] - ), + child: Column(children: [ + Spacer(), + Image.asset( + "assets/image/logo_transparent.png", + color: Colors.white.withValues(alpha: 0.05), + colorBlendMode: BlendMode.modulate, + scale: 0.5, + ), + Spacer(), + ListTile( + title: Text(title), + subtitle: Text(subtitle), + trailing: trailing, + leading: leading, + onTap: _selectProfile, + ) + ]), ); } @@ -384,7 +356,6 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr */ @override Widget getBody(BuildContext context) { - if (!InvenTreeAPI().isConnected()) { return _connectionStatusWidget(context); } @@ -398,7 +369,7 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr int hTiles = smallScreen ? 1 : 2; double aspect = smallScreen ? 5 : 3; double padding = smallScreen ? 2 : 10; - + return GridView.count( crossAxisCount: w > h ? vTiles : hTiles, children: getListTiles(context), @@ -408,12 +379,10 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr mainAxisSpacing: padding, padding: EdgeInsets.all(padding), ); - } @override Widget build(BuildContext context) { - var connected = InvenTreeAPI().isConnected(); var connecting = !connected && InvenTreeAPI().isConnecting(); @@ -426,7 +395,9 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr IconButton( icon: Icon( TablerIcons.server, - color: connected ? COLOR_SUCCESS : (connecting ? COLOR_PROGRESS: COLOR_DANGER), + color: connected + ? COLOR_SUCCESS + : (connecting ? COLOR_PROGRESS : COLOR_DANGER), ), onPressed: _selectProfile, ) @@ -434,7 +405,9 @@ class _InvenTreeHomePageState extends State with BaseWidgetPr ), drawer: InvenTreeDrawer(context), body: getBody(context), - bottomNavigationBar: InvenTreeAPI().isConnected() ? buildBottomAppBar(context, homeKey) : null, + bottomNavigationBar: InvenTreeAPI().isConnected() + ? buildBottomAppBar(context, homeKey) + : null, ); } } diff --git a/lib/widget/notes_widget.dart b/lib/widget/notes_widget.dart index d871dd00..aadc0a13 100644 --- a/lib/widget/notes_widget.dart +++ b/lib/widget/notes_widget.dart @@ -5,7 +5,6 @@ import "package:inventree/widget/refreshable_state.dart"; import "package:flutter_markdown/flutter_markdown.dart"; import "package:inventree/l10.dart"; - /* * A widget for displaying the notes associated with a given model. * We need to pass in the following parameters: @@ -14,7 +13,6 @@ import "package:inventree/l10.dart"; * - Title for the app bar */ class NotesWidget extends StatefulWidget { - const NotesWidget(this.model, {Key? key}) : super(key: key); final InvenTreeModel model; @@ -23,12 +21,10 @@ class NotesWidget extends StatefulWidget { _NotesState createState() => _NotesState(); } - /* * Class representing the state of the NotesWidget */ class _NotesState extends RefreshableState { - _NotesState(); @override @@ -41,30 +37,21 @@ class _NotesState extends RefreshableState { @override List appBarActions(BuildContext context) { - List actions = []; if (widget.model.canEdit) { - actions.add( - IconButton( + actions.add(IconButton( icon: Icon(TablerIcons.edit), tooltip: L10().edit, onPressed: () { - widget.model.editForm( - context, - L10().editNotes, - fields: { - "notes": { - "multiline": true, - } - }, - onSuccess: (data) async { - refresh(context); + widget.model.editForm(context, L10().editNotes, fields: { + "notes": { + "multiline": true, } - ); - } - ) - ); + }, onSuccess: (data) async { + refresh(context); + }); + })); } return actions; @@ -77,5 +64,4 @@ class _NotesState extends RefreshableState { data: widget.model.notes, ); } - -} \ No newline at end of file +} diff --git a/lib/widget/notifications.dart b/lib/widget/notifications.dart index 9b5dabe3..aa8d95f3 100644 --- a/lib/widget/notifications.dart +++ b/lib/widget/notifications.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -8,17 +7,12 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/inventree/notification.dart"; import "package:inventree/widget/refreshable_state.dart"; - class NotificationWidget extends StatefulWidget { - @override _NotificationState createState() => _NotificationState(); - } - class _NotificationState extends RefreshableState { - _NotificationState() : super(); List notifications = []; @@ -29,8 +23,7 @@ class _NotificationState extends RefreshableState { String getAppBarTitle() => L10().notifications; @override - Future request (BuildContext context) async { - + Future request(BuildContext context) async { final results = await InvenTreeNotification().list(); notifications.clear(); @@ -45,8 +38,8 @@ class _NotificationState extends RefreshableState { /* * Dismiss an individual notification entry (mark it as "read") */ - Future dismissNotification(BuildContext context, InvenTreeNotification notification) async { - + Future dismissNotification( + BuildContext context, InvenTreeNotification notification) async { if (mounted) { setState(() { isDismissing = true; @@ -71,36 +64,34 @@ class _NotificationState extends RefreshableState { */ @override List getTiles(BuildContext context) { - List tiles = []; - tiles.add( - ListTile( - title: Text( - L10().notifications, - ), - subtitle: notifications.isEmpty ? Text(L10().notificationsEmpty) : null, - leading: notifications.isEmpty ? Icon(TablerIcons.bell_exclamation) : Icon(TablerIcons.bell), - trailing: Text("${notifications.length}"), - ) - ); + tiles.add(ListTile( + title: Text( + L10().notifications, + ), + subtitle: notifications.isEmpty ? Text(L10().notificationsEmpty) : null, + leading: notifications.isEmpty + ? Icon(TablerIcons.bell_exclamation) + : Icon(TablerIcons.bell), + trailing: Text("${notifications.length}"), + )); for (var notification in notifications) { - tiles.add( - ListTile( - title: Text(notification.name), - subtitle: Text(notification.message), - trailing: IconButton( - icon: Icon(TablerIcons.bookmark), - onPressed: isDismissing ? null : () async { - dismissNotification(context, notification); - }, - ), - ) - ); + tiles.add(ListTile( + title: Text(notification.name), + subtitle: Text(notification.message), + trailing: IconButton( + icon: Icon(TablerIcons.bookmark), + onPressed: isDismissing + ? null + : () async { + dismissNotification(context, notification); + }, + ), + )); } return tiles; - } } diff --git a/lib/widget/order/extra_line_detail.dart b/lib/widget/order/extra_line_detail.dart index bdf7dbce..0c4f8bea 100644 --- a/lib/widget/order/extra_line_detail.dart +++ b/lib/widget/order/extra_line_detail.dart @@ -8,7 +8,6 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/inventree/orders.dart"; - class ExtraLineDetailWidget extends StatefulWidget { const ExtraLineDetailWidget(this.item, {Key? key}) : super(key: key); @@ -18,8 +17,8 @@ class ExtraLineDetailWidget extends StatefulWidget { _ExtraLineDetailWidgetState createState() => _ExtraLineDetailWidgetState(); } -class _ExtraLineDetailWidgetState extends RefreshableState { - +class _ExtraLineDetailWidgetState + extends RefreshableState { _ExtraLineDetailWidgetState(); @override @@ -30,14 +29,11 @@ class _ExtraLineDetailWidgetState extends RefreshableState actions = []; if (widget.item.canEdit) { - actions.add( - IconButton( + actions.add(IconButton( icon: Icon(TablerIcons.edit), onPressed: () { _editLineItem(context); - } - ) - ); + })); } return actions; @@ -53,60 +49,44 @@ class _ExtraLineDetailWidgetState extends RefreshableState _editLineItem(BuildContext context) async { var fields = widget.item.formFields(); - widget.item.editForm( - context, - L10().editLineItem, - fields: fields, + widget.item.editForm(context, L10().editLineItem, fields: fields, onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } @override List getTiles(BuildContext context) { List tiles = []; - tiles.add( - ListTile( - title: Text(L10().reference), - trailing: Text(widget.item.reference), - ) - ); + tiles.add(ListTile( + title: Text(L10().reference), + trailing: Text(widget.item.reference), + )); - tiles.add( - ListTile( - title: Text(L10().description), - trailing: Text(widget.item.description), - ) - ); + tiles.add(ListTile( + title: Text(L10().description), + trailing: Text(widget.item.description), + )); - tiles.add( - ListTile( - title: Text(L10().quantity), - trailing: Text(widget.item.quantity.toString()), - ) - ); + tiles.add(ListTile( + title: Text(L10().quantity), + trailing: Text(widget.item.quantity.toString()), + )); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().unitPrice), trailing: Text( - renderCurrency(widget.item.price, widget.item.priceCurrency) - ) - ) - ); + renderCurrency(widget.item.price, widget.item.priceCurrency)))); if (widget.item.notes.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().notes), - subtitle: Text(widget.item.notes), - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + subtitle: Text(widget.item.notes), + )); } return tiles; } -} \ No newline at end of file +} diff --git a/lib/widget/order/po_extra_line_list.dart b/lib/widget/order/po_extra_line_list.dart index 4c382976..d7fd50c7 100644 --- a/lib/widget/order/po_extra_line_list.dart +++ b/lib/widget/order/po_extra_line_list.dart @@ -9,41 +9,36 @@ import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; - class POExtraLineListWidget extends StatefulWidget { - - const POExtraLineListWidget(this.order, {this.filters = const {}, Key? key}) : super(key: key); + const POExtraLineListWidget(this.order, {this.filters = const {}, Key? key}) + : super(key: key); final InvenTreePurchaseOrder order; final Map filters; @override - _PurchaseOrderExtraLineListWidgetState createState() => _PurchaseOrderExtraLineListWidgetState(); + _PurchaseOrderExtraLineListWidgetState createState() => + _PurchaseOrderExtraLineListWidgetState(); } -class _PurchaseOrderExtraLineListWidgetState extends RefreshableState { - +class _PurchaseOrderExtraLineListWidgetState + extends RefreshableState { _PurchaseOrderExtraLineListWidgetState(); @override String getAppBarTitle() => L10().extraLineItems; Future _addLineItem(BuildContext context) async { - var fields = InvenTreePOExtraLineItem().formFields(); fields["order"]?["value"] = widget.order.pk; - InvenTreePOExtraLineItem().createForm( - context, - L10().lineItemAdd, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + InvenTreePOExtraLineItem().createForm(context, L10().lineItemAdd, + fields: fields, onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } @override @@ -51,15 +46,12 @@ class _PurchaseOrderExtraLineListWidgetState extends RefreshableState actions = []; if (widget.order.canEdit) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus, color: Colors.green), label: L10().lineItemAdd, onTap: () { _addLineItem(context); - } - ) - ); + })); } return actions; @@ -71,35 +63,35 @@ class _PurchaseOrderExtraLineListWidgetState extends RefreshableState filters) : super(filters: filters); + const PaginatedPOExtraLineList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().extraLineItems; @override - _PaginatedPOExtraLineListState createState() => _PaginatedPOExtraLineListState(); - + _PaginatedPOExtraLineListState createState() => + _PaginatedPOExtraLineListState(); } -class _PaginatedPOExtraLineListState extends PaginatedSearchState { - +class _PaginatedPOExtraLineListState + extends PaginatedSearchState { _PaginatedPOExtraLineListState() : super(); @override String get prefix => "po_extra_line_"; @override - Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreePOExtraLineItem().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreePOExtraLineItem() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreePOExtraLineItem line = model as InvenTreePOExtraLineItem; return ListTile( @@ -113,4 +105,4 @@ class _PaginatedPOExtraLineListState extends PaginatedSearchState _POLineDetailWidgetState(); - } - /* * State for the POLineDetailWidget */ class _POLineDetailWidgetState extends RefreshableState { - _POLineDetailWidgetState(); InvenTreeStockLocation? destination; @@ -49,14 +45,12 @@ class _POLineDetailWidgetState extends RefreshableState { List actions = []; if (widget.item.canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - onPressed: () { - _editLineItem(context); - }, - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + onPressed: () { + _editLineItem(context); + }, + )); } return actions; @@ -69,15 +63,12 @@ class _POLineDetailWidgetState extends RefreshableState { if (widget.item.canCreate) { // Receive items if (!widget.item.isComplete) { - buttons.add( - SpeedDialChild( + buttons.add(SpeedDialChild( child: Icon(TablerIcons.transition_right, color: Colors.blue), label: L10().receiveItem, onTap: () async { receiveLineItem(context); - } - ) - ); + })); } } @@ -89,7 +80,9 @@ class _POLineDetailWidgetState extends RefreshableState { await widget.item.reload(); if (widget.item.destinationId > 0) { - InvenTreeStockLocation().get(widget.item.destinationId).then((InvenTreeModel? loc) { + InvenTreeStockLocation() + .get(widget.item.destinationId) + .then((InvenTreeModel? loc) { if (mounted) { if (loc != null && loc is InvenTreeStockLocation) { setState(() { @@ -109,75 +102,68 @@ class _POLineDetailWidgetState extends RefreshableState { }); } } - } // Callback to edit this line item Future _editLineItem(BuildContext context) async { var fields = widget.item.formFields(); - widget.item.editForm( - context, - L10().editLineItem, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + widget.item.editForm(context, L10().editLineItem, fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } - // Launch a form to 'receive' this line item + // Launch a form to 'receive' this line item Future receiveLineItem(BuildContext context) async { - widget.item.receive( - context, - onSuccess: () => { - showSnackIcon(L10().receivedItem, success: true), - refresh(context) - } - ); + widget.item.receive(context, + onSuccess: () => { + showSnackIcon(L10().receivedItem, success: true), + refresh(context) + }); } - + @override List getTiles(BuildContext context) { List tiles = []; // Reference to the part - tiles.add( - ListTile( - title: Text(L10().internalPart), - subtitle: Text(widget.item.partName), - leading: Icon(TablerIcons.box, color: COLOR_ACTION), - trailing: api.getThumbnail(widget.item.partImage), - onTap: () async { - showLoadingOverlay(); - var part = await InvenTreePart().get(widget.item.partId); - hideLoadingOverlay(); + tiles.add(ListTile( + title: Text(L10().internalPart), + subtitle: Text(widget.item.partName), + leading: Icon(TablerIcons.box, color: COLOR_ACTION), + trailing: api.getThumbnail(widget.item.partImage), + onTap: () async { + showLoadingOverlay(); + var part = await InvenTreePart().get(widget.item.partId); + hideLoadingOverlay(); - if (part is InvenTreePart) { - part.goToDetailPage(context); - } - }, - ) - ); + if (part is InvenTreePart) { + part.goToDetailPage(context); + } + }, + )); // Reference to the supplier part - tiles.add( - ListTile( - title: Text(L10().supplierPart), - subtitle: Text(widget.item.SKU), - leading: Icon(TablerIcons.building, color: COLOR_ACTION), - onTap: () async { - showLoadingOverlay(); - var part = await InvenTreeSupplierPart().get(widget.item.supplierPartId); - hideLoadingOverlay(); + tiles.add(ListTile( + title: Text(L10().supplierPart), + subtitle: Text(widget.item.SKU), + leading: Icon(TablerIcons.building, color: COLOR_ACTION), + onTap: () async { + showLoadingOverlay(); + var part = + await InvenTreeSupplierPart().get(widget.item.supplierPartId); + hideLoadingOverlay(); - if (part is InvenTreeSupplierPart) { - Navigator.push(context, MaterialPageRoute(builder: (context) => SupplierPartDetailWidget(part))); - } - }, - ) - ); + if (part is InvenTreeSupplierPart) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SupplierPartDetailWidget(part))); + } + }, + )); // Destination if (destination != null) { @@ -185,75 +171,57 @@ class _POLineDetailWidgetState extends RefreshableState { title: Text(L10().destination), subtitle: Text(destination!.name), leading: Icon(TablerIcons.map_pin, color: COLOR_ACTION), - onTap: () => { - destination!.goToDetailPage(context) - } - )); + onTap: () => {destination!.goToDetailPage(context)})); } // Received quantity - tiles.add( - ListTile( - title: Text(L10().received), - subtitle: ProgressBar(widget.item.progressRatio), - trailing: Text( - widget.item.progressString, - style: TextStyle( - color: widget.item.isComplete ? COLOR_SUCCESS: COLOR_WARNING - ) - ), - leading: Icon(TablerIcons.progress), - ) - ); + tiles.add(ListTile( + title: Text(L10().received), + subtitle: ProgressBar(widget.item.progressRatio), + trailing: Text(widget.item.progressString, + style: TextStyle( + color: widget.item.isComplete ? COLOR_SUCCESS : COLOR_WARNING)), + leading: Icon(TablerIcons.progress), + )); // Reference if (widget.item.reference.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().reference), - subtitle: Text(widget.item.reference), - leading: Icon(TablerIcons.hash), - ) - ); + tiles.add(ListTile( + title: Text(L10().reference), + subtitle: Text(widget.item.reference), + leading: Icon(TablerIcons.hash), + )); } // Pricing information - tiles.add( - ListTile( - title: Text(L10().unitPrice), - leading: Icon(TablerIcons.currency_dollar), - trailing: Text( - renderCurrency(widget.item.purchasePrice, widget.item.purchasePriceCurrency) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().unitPrice), + leading: Icon(TablerIcons.currency_dollar), + trailing: Text(renderCurrency( + widget.item.purchasePrice, widget.item.purchasePriceCurrency)), + )); // Note if (widget.item.notes.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().notes), - subtitle: Text(widget.item.notes), - leading: Icon(TablerIcons.note), - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + subtitle: Text(widget.item.notes), + leading: Icon(TablerIcons.note), + )); } // External link if (widget.item.link.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().link), - subtitle: Text(widget.item.link), - leading: Icon(TablerIcons.link, color: COLOR_ACTION), - onTap: () async { - await openLink(widget.item.link); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().link), + subtitle: Text(widget.item.link), + leading: Icon(TablerIcons.link, color: COLOR_ACTION), + onTap: () async { + await openLink(widget.item.link); + }, + )); } return tiles; } - -} \ No newline at end of file +} diff --git a/lib/widget/order/po_line_list.dart b/lib/widget/order/po_line_list.dart index 5050477e..514de565 100644 --- a/lib/widget/order/po_line_list.dart +++ b/lib/widget/order/po_line_list.dart @@ -16,22 +16,21 @@ import "package:inventree/widget/progress.dart"; * Paginated widget class for displaying a list of purchase order line items */ class PaginatedPOLineList extends PaginatedSearchWidget { - - const PaginatedPOLineList(Map filters) : super(filters: filters); + const PaginatedPOLineList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().lineItems; @override _PaginatedPOLineListState createState() => _PaginatedPOLineListState(); - } /* * State class for PaginatedPOLineList */ -class _PaginatedPOLineListState extends PaginatedSearchState { - +class _PaginatedPOLineListState + extends PaginatedSearchState { _PaginatedPOLineListState() : super(); @override @@ -39,29 +38,30 @@ class _PaginatedPOLineListState extends PaginatedSearchState get orderingOptions => { - "part": L10().part, - "SKU": L10().sku, - "quantity": L10().quantity, - }; + "part": L10().part, + "SKU": L10().sku, + "quantity": L10().quantity, + }; @override Map> get filterOptions => { - "pending": { - "label": L10().outstanding, - "help_text": L10().outstandingOrderDetail, - "tristate": true, - }, - "received": { - "label": L10().received, - "help_text": L10().receivedFilterDetail, - "tristate": true, - } - }; + "pending": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + }, + "received": { + "label": L10().received, + "help_text": L10().receivedFilterDetail, + "tristate": true, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreePOLineItem().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreePOLineItem() + .listPaginated(limit, offset, filters: params); return page; } @@ -71,24 +71,29 @@ class _PaginatedPOLineListState extends PaginatedSearchState POLineDetailWidget(item))); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => POLineDetailWidget(item))); }, ); } else { // Return an error tile return ListTile( title: Text(L10().error), - subtitle: Text("supplier part not defined", style: TextStyle(color: COLOR_DANGER)), + subtitle: Text("supplier part not defined", + style: TextStyle(color: COLOR_DANGER)), ); } } diff --git a/lib/widget/order/purchase_order_detail.dart b/lib/widget/order/purchase_order_detail.dart index 12332ae1..00b2cc67 100644 --- a/lib/widget/order/purchase_order_detail.dart +++ b/lib/widget/order/purchase_order_detail.dart @@ -18,7 +18,6 @@ import "package:inventree/widget/order/po_extra_line_list.dart"; import "package:inventree/widget/stock/location_display.dart"; import "package:inventree/widget/order/po_line_list.dart"; - import "package:inventree/widget/attachment_widget.dart"; import "package:inventree/widget/notes_widget.dart"; import "package:inventree/widget/progress.dart"; @@ -27,13 +26,11 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock/stock_list.dart"; import "package:inventree/preferences.dart"; - /* * Widget for viewing a single PurchaseOrder instance */ class PurchaseOrderDetailWidget extends StatefulWidget { - - const PurchaseOrderDetailWidget(this.order, {Key? key}): super(key: key); + const PurchaseOrderDetailWidget(this.order, {Key? key}) : super(key: key); final InvenTreePurchaseOrder order; @@ -41,11 +38,10 @@ class PurchaseOrderDetailWidget extends StatefulWidget { _PurchaseOrderDetailState createState() => _PurchaseOrderDetailState(); } - -class _PurchaseOrderDetailState extends RefreshableState { - +class _PurchaseOrderDetailState + extends RefreshableState { _PurchaseOrderDetailState(); - + List lines = []; int extraLineCount = 0; @@ -73,15 +69,12 @@ class _PurchaseOrderDetailState extends RefreshableState actions = []; if (widget.order.canEdit) { - actions.add( - IconButton( + actions.add(IconButton( icon: Icon(TablerIcons.edit), tooltip: L10().purchaseOrderEdit, onPressed: () { editOrder(context); - } - ) - ); + })); } return actions; @@ -92,51 +85,38 @@ class _PurchaseOrderDetailState extends RefreshableState actions = []; if (showCameraShortcut && widget.order.canEdit) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.camera, color: Colors.blue), - label: L10().takePicture, - onTap: () async { - _uploadImage(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.camera, color: Colors.blue), + label: L10().takePicture, + onTap: () async { + _uploadImage(context); + })); } if (widget.order.canCreate) { if (widget.order.isPending) { - - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus, color: Colors.green), label: L10().lineItemAdd, onTap: () async { _addLineItem(context); - } - ) - ); + })); - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.send, color: Colors.blue), label: L10().issueOrder, onTap: () async { _issueOrder(context); - } - ) - ); + })); } if (widget.order.isOpen) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_x, color: Colors.red), label: L10().cancelOrder, onTap: () async { _cancelOrder(context); - } - ) - ); + })); } } @@ -145,67 +125,53 @@ class _PurchaseOrderDetailState extends RefreshableState _addLineItem(BuildContext context) async { - var fields = InvenTreePOLineItem().formFields(); // Update part field definition fields["part"]?["hidden"] = false; - fields["part"]?["filters"] = { - "supplier": widget.order.supplierId - }; + fields["part"]?["filters"] = {"supplier": widget.order.supplierId}; fields["order"]?["value"] = widget.order.pk; - InvenTreePOLineItem().createForm( - context, - L10().lineItemAdd, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + InvenTreePOLineItem().createForm(context, L10().lineItemAdd, fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } /// Upload an image against the current PurchaseOrder Future _uploadImage(BuildContext context) async { - - InvenTreePurchaseOrderAttachment().uploadImage( - widget.order.pk, - prefix: widget.order.reference, - ).then((result) => refresh(context)); + InvenTreePurchaseOrderAttachment() + .uploadImage( + widget.order.pk, + prefix: widget.order.reference, + ) + .then((result) => refresh(context)); } /// Issue this order Future _issueOrder(BuildContext context) async { - - confirmationDialog( - L10().issueOrder, "", - icon: TablerIcons.send, - color: Colors.blue, - acceptText: L10().issue, - onAccept: () async { - widget.order.issueOrder().then((dynamic) { - refresh(context); - }); - } - ); + confirmationDialog(L10().issueOrder, "", + icon: TablerIcons.send, + color: Colors.blue, + acceptText: L10().issue, onAccept: () async { + widget.order.issueOrder().then((dynamic) { + refresh(context); + }); + }); } /// Cancel this order Future _cancelOrder(BuildContext context) async { - - confirmationDialog( - L10().cancelOrder, "", - icon: TablerIcons.circle_x, - color: Colors.red, - acceptText: L10().cancel, - onAccept: () async { - widget.order.cancelOrder().then((dynamic) { - refresh(context); - }); - } - ); + confirmationDialog(L10().cancelOrder, "", + icon: TablerIcons.circle_x, + color: Colors.red, + acceptText: L10().cancel, onAccept: () async { + widget.order.cancelOrder().then((dynamic) { + refresh(context); + }); + }); } @override @@ -213,25 +179,22 @@ class _PurchaseOrderDetailState extends RefreshableState actions = []; if (api.supportsBarcodePOReceiveEndpoint && widget.order.isPlaced) { - actions.add( - SpeedDialChild( - child: Icon(Icons.barcode_reader), - label: L10().scanReceivedParts, - onTap:() async { - scanBarcode( - context, - handler: POReceiveBarcodeHandler(purchaseOrder: widget.order), - ).then((value) { - refresh(context); - }); - }, - ) - ); + actions.add(SpeedDialChild( + child: Icon(Icons.barcode_reader), + label: L10().scanReceivedParts, + onTap: () async { + scanBarcode( + context, + handler: POReceiveBarcodeHandler(purchaseOrder: widget.order), + ).then((value) { + refresh(context); + }); + }, + )); } if (widget.order.isPending && api.supportsBarcodePOAddLineEndpoint) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus, color: COLOR_SUCCESS), label: L10().lineItemAdd, onTap: () async { @@ -239,15 +202,12 @@ class _PurchaseOrderDetailState extends RefreshableState request(BuildContext context) async { await widget.order.reload(); @@ -256,8 +216,11 @@ class _PurchaseOrderDetailState extends RefreshableState 0) { - InvenTreeStockLocation().get(widget.order.destinationId).then((InvenTreeModel? loc) { + if (api.supportsPurchaseOrderDestination && + widget.order.destinationId > 0) { + InvenTreeStockLocation() + .get(widget.order.destinationId) + .then((InvenTreeModel? loc) { if (mounted) { if (loc != null && loc is InvenTreeStockLocation) { setState(() { @@ -298,7 +266,8 @@ class _PurchaseOrderDetailState extends RefreshableState editOrder(BuildContext context) async { + Future editOrder(BuildContext context) async { var fields = widget.order.formFields(); // Cannot edit supplier field from here @@ -324,39 +293,30 @@ class _PurchaseOrderDetailState extends RefreshableState orderTiles(BuildContext context) { - List tiles = []; InvenTreeCompany? supplier = widget.order.supplier; @@ -366,7 +326,8 @@ class _PurchaseOrderDetailState extends RefreshableState { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => LocationDisplayWidget(destination) - ) - ) - } - )); + title: Text(L10().destination), + subtitle: Text(destination!.name), + leading: Icon(TablerIcons.map_pin, color: COLOR_ACTION), + onTap: () => { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + LocationDisplayWidget(destination))) + })); } - Color lineColor = completedLines < widget.order.lineItemCount ? COLOR_WARNING : COLOR_SUCCESS; + Color lineColor = completedLines < widget.order.lineItemCount + ? COLOR_WARNING + : COLOR_SUCCESS; tiles.add(ListTile( title: Text(L10().lineItems), @@ -416,7 +377,8 @@ class _PurchaseOrderDetailState extends RefreshableState { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => POExtraLineListWidget(widget.order, filters: {"order": widget.order.pk.toString()}) - ) - ) + context, + MaterialPageRoute( + builder: (context) => POExtraLineListWidget(widget.order, + filters: {"order": widget.order.pk.toString()}))) }, )); tiles.add(ListTile( title: Text(L10().totalPrice), leading: Icon(TablerIcons.currency_dollar), - trailing: Text( - renderCurrency(widget.order.totalPrice, widget.order.totalPriceCurrency) - ), + trailing: Text(renderCurrency( + widget.order.totalPrice, widget.order.totalPriceCurrency)), )); if (widget.order.issueDate.isNotEmpty) { @@ -475,54 +435,44 @@ class _PurchaseOrderDetailState extends RefreshableState NotesWidget(widget.order) - ) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + leading: Icon(TablerIcons.note, color: COLOR_ACTION), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => NotesWidget(widget.order))); + }, + )); // Attachments - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( + tiles.add(ListTile( + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( builder: (context) => AttachmentWidget( InvenTreePurchaseOrderAttachment(), widget.order.pk, widget.order.reference, - widget.order.canEdit - ) - ) - ); - }, - ) - ); + widget.order.canEdit))); + }, + )); return tiles; - } @override @@ -533,7 +483,7 @@ class _PurchaseOrderDetailState extends RefreshableState getTabs(BuildContext context) { return [ diff --git a/lib/widget/order/purchase_order_list.dart b/lib/widget/order/purchase_order_list.dart index 8351ac46..df39dc56 100644 --- a/lib/widget/order/purchase_order_list.dart +++ b/lib/widget/order/purchase_order_list.dart @@ -16,18 +16,18 @@ import "package:inventree/inventree/purchase_order.dart"; * Widget class for displaying a list of Purchase Orders */ class PurchaseOrderListWidget extends StatefulWidget { - - const PurchaseOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); + const PurchaseOrderListWidget({this.filters = const {}, Key? key}) + : super(key: key); final Map filters; @override - _PurchaseOrderListWidgetState createState() => _PurchaseOrderListWidgetState(); + _PurchaseOrderListWidgetState createState() => + _PurchaseOrderListWidgetState(); } - -class _PurchaseOrderListWidgetState extends RefreshableState { - +class _PurchaseOrderListWidgetState + extends RefreshableState { _PurchaseOrderListWidgetState(); @override @@ -38,15 +38,12 @@ class _PurchaseOrderListWidgetState extends RefreshableState actions = []; if (InvenTreePurchaseOrder().canCreate) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus), label: L10().purchaseOrderCreate, onTap: () { _createPurchaseOrder(context); - } - ) - ); + })); } return actions; @@ -59,19 +56,15 @@ class _PurchaseOrderListWidgetState extends RefreshableState data = result as Map; + InvenTreePurchaseOrder().createForm(context, L10().purchaseOrderCreate, + fields: fields, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var order = InvenTreePurchaseOrder.fromJson(data); - order.goToDetailPage(context); - } + if (data.containsKey("pk")) { + var order = InvenTreePurchaseOrder.fromJson(data); + order.goToDetailPage(context); } - ); + }); } @override @@ -79,18 +72,16 @@ class _PurchaseOrderListWidgetState extends RefreshableState actions = []; if (api.supportsBarcodePOReceiveEndpoint) { - actions.add( - SpeedDialChild( - child: Icon(Icons.barcode_reader), - label: L10().scanReceivedParts, - onTap:() async { - scanBarcode( - context, - handler: POReceiveBarcodeHandler(), - ); - }, - ) - ); + actions.add(SpeedDialChild( + child: Icon(Icons.barcode_reader), + label: L10().scanReceivedParts, + onTap: () async { + scanBarcode( + context, + handler: POReceiveBarcodeHandler(), + ); + }, + )); } return actions; @@ -102,22 +93,20 @@ class _PurchaseOrderListWidgetState extends RefreshableState filters) : super(filters: filters); + const PaginatedPurchaseOrderList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().purchaseOrders; @override - _PaginatedPurchaseOrderListState createState() => _PaginatedPurchaseOrderListState(); - + _PaginatedPurchaseOrderListState createState() => + _PaginatedPurchaseOrderListState(); } - -class _PaginatedPurchaseOrderListState extends PaginatedSearchState { - +class _PaginatedPurchaseOrderListState + extends PaginatedSearchState { _PaginatedPurchaseOrderListState() : super(); @override @@ -125,51 +114,53 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState get orderingOptions => { - "reference": L10().reference, - "supplier__name": L10().supplier, - "status": L10().status, - "target_date": L10().targetDate, - }; + "reference": L10().reference, + "supplier__name": L10().supplier, + "status": L10().status, + "target_date": L10().targetDate, + }; @override Map> get filterOptions => { - "outstanding": { - "label": L10().outstanding, - "help_text": L10().outstandingOrderDetail, - "tristate": true, - }, - "overdue": { - "label": L10().overdue, - "help_text": L10().overdueDetail, - "tristate": true, - }, - "assigned_to_me": { - "label": L10().assignedToMe, - "help_text": L10().assignedToMeDetail, - "tristate": true, - } - }; + "outstanding": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + }, + "overdue": { + "label": L10().overdue, + "help_text": L10().overdueDetail, + "tristate": true, + }, + "assigned_to_me": { + "label": L10().assignedToMe, + "help_text": L10().assignedToMeDetail, + "tristate": true, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - + Future requestPage( + int limit, int offset, Map params) async { await InvenTreeAPI().PurchaseOrderStatus.load(); - final page = await InvenTreePurchaseOrder().listPaginated(limit, offset, filters: params); + final page = await InvenTreePurchaseOrder() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreePurchaseOrder order = model as InvenTreePurchaseOrder; InvenTreeCompany? supplier = order.supplier; - + return ListTile( title: Text(order.reference), subtitle: Text(order.description), - leading: supplier == null ? null : InvenTreeAPI().getThumbnail(supplier.thumbnail), + leading: supplier == null + ? null + : InvenTreeAPI().getThumbnail(supplier.thumbnail), trailing: Text( InvenTreeAPI().PurchaseOrderStatus.label(order.status), style: TextStyle( @@ -181,4 +172,4 @@ class _PaginatedPurchaseOrderListState extends PaginatedSearchState _SalesOrderDetailState(); } - class _SalesOrderDetailState extends RefreshableState { - _SalesOrderDetailState(); List lines = []; @@ -62,14 +58,12 @@ class _SalesOrderDetailState extends RefreshableState { List actions = []; if (widget.order.canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - onPressed: () { - editOrder(context); - }, - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + onPressed: () { + editOrder(context); + }, + )); } return actions; @@ -77,21 +71,15 @@ class _SalesOrderDetailState extends RefreshableState { // Add a new shipment against this sales order Future _addShipment(BuildContext context) async { - var fields = InvenTreeSalesOrderShipment().formFields(); fields["order"]?["value"] = widget.order.pk; fields["order"]?["hidden"] = true; - InvenTreeSalesOrderShipment().createForm( - context, - L10().shipmentAdd, - fields: fields, - onSuccess: (result) async { - refresh(context); - } - ); - + InvenTreeSalesOrderShipment().createForm(context, L10().shipmentAdd, + fields: fields, onSuccess: (result) async { + refresh(context); + }); } // Add a new line item to this sales order @@ -101,54 +89,44 @@ class _SalesOrderDetailState extends RefreshableState { fields["order"]?["value"] = widget.order.pk; fields["order"]?["hidden"] = true; - InvenTreeSOLineItem().createForm( - context, - L10().lineItemAdd, - fields: fields, + InvenTreeSOLineItem().createForm(context, L10().lineItemAdd, fields: fields, onSuccess: (result) async { - refresh(context); - } - ); + refresh(context); + }); } /// Upload an image for this order Future _uploadImage(BuildContext context) async { - InvenTreeSalesOrderAttachment().uploadImage( - widget.order.pk, - prefix: widget.order.reference, - ).then((result) => refresh(context)); + InvenTreeSalesOrderAttachment() + .uploadImage( + widget.order.pk, + prefix: widget.order.reference, + ) + .then((result) => refresh(context)); } /// Issue this order Future _issueOrder(BuildContext context) async { - - confirmationDialog( - L10().issueOrder, "", + confirmationDialog(L10().issueOrder, "", icon: TablerIcons.send, color: Colors.blue, - acceptText: L10().issue, - onAccept: () async { - widget.order.issueOrder().then((dynamic) { - refresh(context); - }); - } - ); + acceptText: L10().issue, onAccept: () async { + widget.order.issueOrder().then((dynamic) { + refresh(context); + }); + }); } /// Cancel this order Future _cancelOrder(BuildContext context) async { - - confirmationDialog( - L10().cancelOrder, "", + confirmationDialog(L10().cancelOrder, "", icon: TablerIcons.circle_x, color: Colors.red, - acceptText: L10().cancel, - onAccept: () async { - await widget.order.cancelOrder().then((dynamic) { - refresh(context); - }); - } - ); + acceptText: L10().cancel, onAccept: () async { + await widget.order.cancelOrder().then((dynamic) { + refresh(context); + }); + }); } @override @@ -156,62 +134,48 @@ class _SalesOrderDetailState extends RefreshableState { List actions = []; if (showCameraShortcut && widget.order.canEdit) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.camera, color: Colors.blue), - label: L10().takePicture, - onTap: () async { - _uploadImage(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.camera, color: Colors.blue), + label: L10().takePicture, + onTap: () async { + _uploadImage(context); + })); } if (widget.order.isPending) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.send, color: Colors.blue), - label: L10().issueOrder, - onTap: () async { - _issueOrder(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.send, color: Colors.blue), + label: L10().issueOrder, + onTap: () async { + _issueOrder(context); + })); } if (widget.order.isOpen) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_x, color: Colors.red), - label: L10().cancelOrder, - onTap: () async { - _cancelOrder(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_x, color: Colors.red), + label: L10().cancelOrder, + onTap: () async { + _cancelOrder(context); + })); } // Add line item - if ((widget.order.isPending || widget.order.isInProgress) && InvenTreeSOLineItem().canCreate) { - actions.add( - SpeedDialChild( + if ((widget.order.isPending || widget.order.isInProgress) && + InvenTreeSOLineItem().canCreate) { + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus, color: Colors.green), label: L10().lineItemAdd, onTap: () async { _addLineItem(context); - } - ) - ); + })); - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus, color: Colors.green), label: L10().shipmentAdd, onTap: () async { _addShipment(context); - } - ) - ); + })); } return actions; @@ -221,9 +185,9 @@ class _SalesOrderDetailState extends RefreshableState { List barcodeButtons(BuildContext context) { List actions = []; - if ((widget.order.isInProgress || widget.order.isPending) && InvenTreeSOLineItem().canCreate) { - actions.add( - SpeedDialChild( + if ((widget.order.isInProgress || widget.order.isPending) && + InvenTreeSOLineItem().canCreate) { + actions.add(SpeedDialChild( child: Icon(Icons.barcode_reader), label: L10().lineItemAdd, onTap: () async { @@ -231,25 +195,18 @@ class _SalesOrderDetailState extends RefreshableState { context, handler: SOAddItemBarcodeHandler(salesOrder: widget.order), ); - } - ) - ); + })); if (api.supportsBarcodeSOAllocateEndpoint) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.transition_right), label: L10().allocateStock, onTap: () async { - scanBarcode( - context, - handler: SOAllocateStockHandler( - salesOrder: widget.order, - ) - ); - } - ) - ); + scanBarcode(context, + handler: SOAllocateStockHandler( + salesOrder: widget.order, + )); + })); } } @@ -261,10 +218,15 @@ class _SalesOrderDetailState extends RefreshableState { await widget.order.reload(); await api.SalesOrderStatus.load(); - supportsProjectCodes = api.supportsProjectCodes && await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED", backup: true); - showCameraShortcut = await InvenTreeSettingsManager().getBool(INV_SO_SHOW_CAMERA, true); + supportsProjectCodes = api.supportsProjectCodes && + await api.getGlobalBooleanSetting("PROJECT_CODES_ENABLED", + backup: true); + showCameraShortcut = + await InvenTreeSettingsManager().getBool(INV_SO_SHOW_CAMERA, true); - InvenTreeSalesOrderAttachment().countAttachments(widget.order.pk).then((int value) { + InvenTreeSalesOrderAttachment() + .countAttachments(widget.order.pk) + .then((int value) { if (mounted) { setState(() { attachmentCount = value; @@ -273,7 +235,8 @@ class _SalesOrderDetailState extends RefreshableState { }); // Count number of "extra line items" against this order - InvenTreeSOExtraLineItem().count(filters: {"order": widget.order.pk.toString() }).then((int value) { + InvenTreeSOExtraLineItem().count( + filters: {"order": widget.order.pk.toString()}).then((int value) { if (mounted) { setState(() { extraLineCount = value; @@ -298,15 +261,11 @@ class _SalesOrderDetailState extends RefreshableState { fields.remove("project_code"); } - widget.order.editForm( - context, - L10().salesOrderEdit, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().salesOrderUpdated, success: true); - } - ); + widget.order.editForm(context, L10().salesOrderEdit, fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().salesOrderUpdated, success: true); + }); } // Construct header tile @@ -314,45 +273,40 @@ class _SalesOrderDetailState extends RefreshableState { InvenTreeCompany? customer = widget.order.customer; return Card( - child: ListTile( - title: Text(widget.order.reference), - subtitle: Text(widget.order.description), - leading: customer == null ? null : api.getThumbnail(customer.thumbnail), - trailing: Text( - api.SalesOrderStatus.label(widget.order.status), - style: TextStyle( - color: api.SalesOrderStatus.color(widget.order.status) - ), - ), - ) - ); + child: ListTile( + title: Text(widget.order.reference), + subtitle: Text(widget.order.description), + leading: customer == null ? null : api.getThumbnail(customer.thumbnail), + trailing: Text( + api.SalesOrderStatus.label(widget.order.status), + style: + TextStyle(color: api.SalesOrderStatus.color(widget.order.status)), + ), + )); } List orderTiles(BuildContext context) { - - List tiles = [ - headerTile(context) - ]; + List tiles = [headerTile(context)]; InvenTreeCompany? customer = widget.order.customer; if (supportsProjectCodes && widget.order.hasProjectCode) { tiles.add(ListTile( title: Text(L10().projectCode), - subtitle: Text("${widget.order.projectCode} - ${widget.order.projectCodeDescription}"), + subtitle: Text( + "${widget.order.projectCode} - ${widget.order.projectCodeDescription}"), leading: Icon(TablerIcons.list), )); } if (customer != null) { tiles.add(ListTile( - title: Text(L10().customer), - subtitle: Text(customer.name), - leading: Icon(TablerIcons.user, color: COLOR_ACTION), - onTap: () { - customer.goToDetailPage(context); - } - )); + title: Text(L10().customer), + subtitle: Text(customer.name), + leading: Icon(TablerIcons.user, color: COLOR_ACTION), + onTap: () { + customer.goToDetailPage(context); + })); } if (widget.order.customerReference.isNotEmpty) { @@ -367,12 +321,12 @@ class _SalesOrderDetailState extends RefreshableState { tiles.add(ListTile( title: Text(L10().lineItems), - subtitle: ProgressBar( - widget.order.completedLineItemCount.toDouble(), - maximum: widget.order.lineItemCount.toDouble() - ), + subtitle: ProgressBar(widget.order.completedLineItemCount.toDouble(), + maximum: widget.order.lineItemCount.toDouble()), leading: Icon(TablerIcons.clipboard_check), - trailing: Text("${widget.order.completedLineItemCount} / ${widget.order.lineItemCount}", style: TextStyle(color: lineColor)), + trailing: Text( + "${widget.order.completedLineItemCount} / ${widget.order.lineItemCount}", + style: TextStyle(color: lineColor)), )); // Extra line items @@ -384,9 +338,8 @@ class _SalesOrderDetailState extends RefreshableState { Navigator.push( context, MaterialPageRoute( - builder: (context) => SOExtraLineListWidget(widget.order, filters: {"order": widget.order.pk.toString()}) - ) - ) + builder: (context) => SOExtraLineListWidget(widget.order, + filters: {"order": widget.order.pk.toString()}))) }, )); @@ -394,12 +347,12 @@ class _SalesOrderDetailState extends RefreshableState { if (widget.order.shipmentCount > 0) { tiles.add(ListTile( title: Text(L10().shipments), - subtitle: ProgressBar( - widget.order.completedShipmentCount.toDouble(), - maximum: widget.order.shipmentCount.toDouble() - ), + subtitle: ProgressBar(widget.order.completedShipmentCount.toDouble(), + maximum: widget.order.shipmentCount.toDouble()), leading: Icon(TablerIcons.truck_delivery), - trailing: Text("${widget.order.completedShipmentCount} / ${widget.order.shipmentCount}", style: TextStyle(color: lineColor)), + trailing: Text( + "${widget.order.completedShipmentCount} / ${widget.order.shipmentCount}", + style: TextStyle(color: lineColor)), )); } @@ -430,51 +383,42 @@ class _SalesOrderDetailState extends RefreshableState { } // Responsible "owner" - if (widget.order.responsibleName.isNotEmpty && widget.order.responsibleLabel.isNotEmpty) { + if (widget.order.responsibleName.isNotEmpty && + widget.order.responsibleLabel.isNotEmpty) { tiles.add(ListTile( title: Text(L10().responsible), - leading: Icon(widget.order.responsibleLabel == "group" ? TablerIcons.users : TablerIcons.user), - trailing: Text(widget.order.responsibleName) - )); + leading: Icon(widget.order.responsibleLabel == "group" + ? TablerIcons.users + : TablerIcons.user), + trailing: Text(widget.order.responsibleName))); } // Notes tile - tiles.add( - ListTile( - title: Text(L10().notes), - leading: Icon(TablerIcons.note, color: COLOR_ACTION), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => NotesWidget(widget.order) - ) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + leading: Icon(TablerIcons.note, color: COLOR_ACTION), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => NotesWidget(widget.order))); + }, + )); // Attachments - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreeSalesOrderAttachment(), - widget.order.pk, - widget.order.reference, - widget.order.canEdit - ) - ) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AttachmentWidget( + InvenTreeSalesOrderAttachment(), + widget.order.pk, + widget.order.reference, + widget.order.canEdit))); + }, + )); return tiles; } @@ -496,5 +440,4 @@ class _SalesOrderDetailState extends RefreshableState { PaginatedSOShipmentList({"order": widget.order.pk.toString()}), ]; } - } diff --git a/lib/widget/order/sales_order_list.dart b/lib/widget/order/sales_order_list.dart index 46264c47..2150d1de 100644 --- a/lib/widget/order/sales_order_list.dart +++ b/lib/widget/order/sales_order_list.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_speed_dial/flutter_speed_dial.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -12,20 +11,18 @@ import "package:inventree/api.dart"; import "package:inventree/inventree/company.dart"; import "package:inventree/inventree/model.dart"; - class SalesOrderListWidget extends StatefulWidget { - - const SalesOrderListWidget({this.filters = const {}, Key? key}) : super(key: key); + const SalesOrderListWidget({this.filters = const {}, Key? key}) + : super(key: key); final Map filters; @override _SalesOrderListWidgetState createState() => _SalesOrderListWidgetState(); - } -class _SalesOrderListWidgetState extends RefreshableState { - +class _SalesOrderListWidgetState + extends RefreshableState { _SalesOrderListWidgetState(); @override @@ -36,15 +33,12 @@ class _SalesOrderListWidgetState extends RefreshableState List actions = []; if (InvenTreeSalesOrder().canCreate) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_plus), - label: L10().salesOrderCreate, - onTap: () { - _createSalesOrder(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_plus), + label: L10().salesOrderCreate, + onTap: () { + _createSalesOrder(context); + })); } return actions; @@ -57,19 +51,15 @@ class _SalesOrderListWidgetState extends RefreshableState // Cannot set contact until company is locked in fields.remove("contact"); - InvenTreeSalesOrder().createForm( - context, - L10().salesOrderCreate, - fields: fields, - onSuccess: (result) async { - Map data = result as Map; + InvenTreeSalesOrder().createForm(context, L10().salesOrderCreate, + fields: fields, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var order = InvenTreeSalesOrder.fromJson(data); - order.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var order = InvenTreeSalesOrder.fromJson(data); + order.goToDetailPage(context); + } + }); } @override @@ -82,25 +72,22 @@ class _SalesOrderListWidgetState extends RefreshableState Widget getBody(BuildContext context) { return PaginatedSalesOrderList(widget.filters); } - } - class PaginatedSalesOrderList extends PaginatedSearchWidget { - - const PaginatedSalesOrderList(Map filters) : super(filters: filters); + const PaginatedSalesOrderList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().salesOrders; @override - _PaginatedSalesOrderListState createState() => _PaginatedSalesOrderListState(); - + _PaginatedSalesOrderListState createState() => + _PaginatedSalesOrderListState(); } - -class _PaginatedSalesOrderListState extends PaginatedSearchState { - +class _PaginatedSalesOrderListState + extends PaginatedSearchState { _PaginatedSalesOrderListState() : super(); @override @@ -108,62 +95,59 @@ class _PaginatedSalesOrderListState extends PaginatedSearchState get orderingOptions => { - "reference": L10().reference, - "status": L10().status, - "target_date": L10().targetDate, - "customer__name": L10().customer, - }; + "reference": L10().reference, + "status": L10().status, + "target_date": L10().targetDate, + "customer__name": L10().customer, + }; @override Map> get filterOptions => { - "outstanding": { - "label": L10().outstanding, - "help_text": L10().outstandingOrderDetail, - "tristate": true, - }, - "overdue": { - "label": L10().overdue, - "help_text": L10().overdueDetail, - "tristate": true, - }, - "assigned_to_me": { - "label": L10().assignedToMe, - "help_text": L10().assignedToMeDetail, - "tristate": true, - } - }; + "outstanding": { + "label": L10().outstanding, + "help_text": L10().outstandingOrderDetail, + "tristate": true, + }, + "overdue": { + "label": L10().overdue, + "help_text": L10().overdueDetail, + "tristate": true, + }, + "assigned_to_me": { + "label": L10().assignedToMe, + "help_text": L10().assignedToMeDetail, + "tristate": true, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - + Future requestPage( + int limit, int offset, Map params) async { await InvenTreeAPI().SalesOrderStatus.load(); - final page = await InvenTreeSalesOrder().listPaginated(limit, offset, filters: params); + final page = await InvenTreeSalesOrder() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeSalesOrder order = model as InvenTreeSalesOrder; InvenTreeCompany? customer = order.customer; return ListTile( - title: Text(order.reference), - subtitle: Text(order.description), - leading: customer == null ? null : InvenTreeAPI().getThumbnail(customer.thumbnail), - trailing: Text( - InvenTreeAPI().SalesOrderStatus.label(order.status), - style: TextStyle( - color: InvenTreeAPI().SalesOrderStatus.color(order.status), - ) - ), - onTap: () async { - order.goToDetailPage(context); - } - ); - + title: Text(order.reference), + subtitle: Text(order.description), + leading: customer == null + ? null + : InvenTreeAPI().getThumbnail(customer.thumbnail), + trailing: Text(InvenTreeAPI().SalesOrderStatus.label(order.status), + style: TextStyle( + color: InvenTreeAPI().SalesOrderStatus.color(order.status), + )), + onTap: () async { + order.goToDetailPage(context); + }); } - -} \ No newline at end of file +} diff --git a/lib/widget/order/so_extra_line_list.dart b/lib/widget/order/so_extra_line_list.dart index efbce2e3..e52e0b23 100644 --- a/lib/widget/order/so_extra_line_list.dart +++ b/lib/widget/order/so_extra_line_list.dart @@ -11,41 +11,36 @@ import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/widget/snacks.dart"; - class SOExtraLineListWidget extends StatefulWidget { - - const SOExtraLineListWidget(this.order, {this.filters = const {}, Key? key}) : super(key: key); + const SOExtraLineListWidget(this.order, {this.filters = const {}, Key? key}) + : super(key: key); final InvenTreeSalesOrder order; final Map filters; @override - _SalesOrderExtraLineListWidgetState createState() => _SalesOrderExtraLineListWidgetState(); + _SalesOrderExtraLineListWidgetState createState() => + _SalesOrderExtraLineListWidgetState(); } -class _SalesOrderExtraLineListWidgetState extends RefreshableState { - +class _SalesOrderExtraLineListWidgetState + extends RefreshableState { _SalesOrderExtraLineListWidgetState(); @override String getAppBarTitle() => L10().extraLineItems; Future _addLineItem(BuildContext context) async { - var fields = InvenTreeSOExtraLineItem().formFields(); fields["order"]?["value"] = widget.order.pk; - InvenTreeSOExtraLineItem().createForm( - context, - L10().lineItemAdd, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + InvenTreeSOExtraLineItem().createForm(context, L10().lineItemAdd, + fields: fields, onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } @override @@ -53,15 +48,12 @@ class _SalesOrderExtraLineListWidgetState extends RefreshableState actions = []; if (widget.order.canEdit) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_plus, color: Colors.green), - label: L10().lineItemAdd, - onTap: () { - _addLineItem(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_plus, color: Colors.green), + label: L10().lineItemAdd, + onTap: () { + _addLineItem(context); + })); } return actions; @@ -73,35 +65,35 @@ class _SalesOrderExtraLineListWidgetState extends RefreshableState filters) : super(filters: filters); + const PaginatedSOExtraLineList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().extraLineItems; @override - _PaginatedSOExtraLineListState createState() => _PaginatedSOExtraLineListState(); - + _PaginatedSOExtraLineListState createState() => + _PaginatedSOExtraLineListState(); } -class _PaginatedSOExtraLineListState extends PaginatedSearchState { - +class _PaginatedSOExtraLineListState + extends PaginatedSearchState { _PaginatedSOExtraLineListState() : super(); @override String get prefix => "so_extra_line_"; @override - Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreeSOExtraLineItem().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreeSOExtraLineItem() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeSOExtraLineItem line = model as InvenTreeSOExtraLineItem; return ListTile( @@ -115,4 +107,4 @@ class _PaginatedSOExtraLineListState extends PaginatedSearchState _SOLineDetailWidgetState(); - } - class _SOLineDetailWidgetState extends RefreshableState { - _SOLineDetailWidgetState(); InvenTreeSalesOrder? order; @@ -62,7 +55,6 @@ class _SOLineDetailWidgetState extends RefreshableState { } Future _allocateStock(BuildContext context) async { - if (order == null) { return; } @@ -76,22 +68,13 @@ class _SOLineDetailWidgetState extends RefreshableState { "part": widget.item.partId.toString() }; fields["quantity"]?["value"] = widget.item.unallocatedQuantity.toString(); - fields["shipment"]?["filters"] = { - "order": order!.pk.toString() - }; - - launchApiForm( - context, - L10().allocateStock, - order!.allocate_url, - fields, - method: "POST", - icon: TablerIcons.transition_right, - onSuccess: (data) async { - refresh(context); - } - ); + fields["shipment"]?["filters"] = {"order": order!.pk.toString()}; + launchApiForm(context, L10().allocateStock, order!.allocate_url, fields, + method: "POST", + icon: TablerIcons.transition_right, onSuccess: (data) async { + refresh(context); + }); } Future _editLineItem(BuildContext context) async { @@ -102,32 +85,24 @@ class _SOLineDetailWidgetState extends RefreshableState { fields["part"]?["hidden"] = true; } - widget.item.editForm( - context, - L10().editLineItem, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().lineItemUpdated, success: true); - } - ); + widget.item.editForm(context, L10().editLineItem, fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().lineItemUpdated, success: true); + }); } @override List actionButtons(BuildContext context) { - List buttons = []; if (order != null && order!.isOpen) { - buttons.add( - SpeedDialChild( + buttons.add(SpeedDialChild( child: Icon(TablerIcons.transition_right, color: Colors.blue), label: L10().allocateStock, onTap: () async { _allocateStock(context); - } - ) - ); + })); } return buttons; @@ -138,23 +113,15 @@ class _SOLineDetailWidgetState extends RefreshableState { List actions = []; if (order != null && order!.isOpen && InvenTreeSOLineItem().canCreate) { - if (api.supportsBarcodeSOAllocateEndpoint) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.transition_right), - label: L10().allocateStock, - onTap: () async { - scanBarcode( - context, + actions.add(SpeedDialChild( + child: Icon(TablerIcons.transition_right), + label: L10().allocateStock, + onTap: () async { + scanBarcode(context, handler: SOAllocateStockHandler( - salesOrder: order, - lineItem: widget.item - ) - ); - } - ) - ); + salesOrder: order, lineItem: widget.item)); + })); } } @@ -179,8 +146,7 @@ class _SOLineDetailWidgetState extends RefreshableState { List tiles = []; // Reference to the part - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().part), subtitle: Text(widget.item.partName), leading: Icon(TablerIcons.box, color: COLOR_ACTION), @@ -193,85 +159,64 @@ class _SOLineDetailWidgetState extends RefreshableState { if (part is InvenTreePart) { part.goToDetailPage(context); } - } - ) - ); + })); // Available quantity - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().availableStock), leading: Icon(TablerIcons.packages), - trailing: Text(simpleNumberString(widget.item.availableStock)) - ) - ); + trailing: Text(simpleNumberString(widget.item.availableStock)))); // Allocated quantity - tiles.add( - ListTile( + tiles.add(ListTile( leading: Icon(TablerIcons.clipboard_check), title: Text(L10().allocated), subtitle: ProgressBar(widget.item.allocatedRatio), - trailing: Text( - widget.item.allocatedString, - style: TextStyle( - color: widget.item.isAllocated ? COLOR_SUCCESS : COLOR_WARNING - ) - ) - ) - ); + trailing: Text(widget.item.allocatedString, + style: TextStyle( + color: + widget.item.isAllocated ? COLOR_SUCCESS : COLOR_WARNING)))); // Shipped quantity - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().shipped), subtitle: ProgressBar(widget.item.progressRatio), trailing: Text( widget.item.progressString, style: TextStyle( - color: widget.item.isComplete ? COLOR_SUCCESS : COLOR_WARNING - ), + color: widget.item.isComplete ? COLOR_SUCCESS : COLOR_WARNING), ), - leading: Icon(TablerIcons.truck) - ) - ); + leading: Icon(TablerIcons.truck))); // Reference if (widget.item.reference.isNotEmpty) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().reference), subtitle: Text(widget.item.reference), - leading: Icon(TablerIcons.hash) - ) - ); + leading: Icon(TablerIcons.hash))); } // Note if (widget.item.notes.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().notes), - subtitle: Text(widget.item.notes), - leading: Icon(TablerIcons.note), - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + subtitle: Text(widget.item.notes), + leading: Icon(TablerIcons.note), + )); } // External link if (widget.item.link.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().link), - subtitle: Text(widget.item.link), - leading: Icon(TablerIcons.link, color: COLOR_ACTION), - onTap: () async { - await openLink(widget.item.link); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().link), + subtitle: Text(widget.item.link), + leading: Icon(TablerIcons.link, color: COLOR_ACTION), + onTap: () async { + await openLink(widget.item.link); + }, + )); } return tiles; } -} \ No newline at end of file +} diff --git a/lib/widget/order/so_line_list.dart b/lib/widget/order/so_line_list.dart index a4c74548..ae7f087f 100644 --- a/lib/widget/order/so_line_list.dart +++ b/lib/widget/order/so_line_list.dart @@ -9,28 +9,26 @@ import "package:inventree/api.dart"; import "package:inventree/app_colors.dart"; import "package:inventree/widget/progress.dart"; - /* * Paginated widget class for displaying a list of sales order line items */ class PaginatedSOLineList extends PaginatedSearchWidget { - const PaginatedSOLineList(Map filters) : super(filters: filters); + const PaginatedSOLineList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().lineItems; @override _PaginatedSOLineListState createState() => _PaginatedSOLineListState(); - } - /* * State class for PaginatedSOLineList */ -class _PaginatedSOLineListState extends PaginatedSearchState { - +class _PaginatedSOLineListState + extends PaginatedSearchState { _PaginatedSOLineListState() : super(); @override @@ -38,18 +36,18 @@ class _PaginatedSOLineListState extends PaginatedSearchState get orderingOptions => { - "part": L10().part, - "quantity": L10().quantity, - }; + "part": L10().part, + "quantity": L10().quantity, + }; @override - Map> get filterOptions => { - - }; + Map> get filterOptions => {}; @override - Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreeSOLineItem().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreeSOLineItem() + .listPaginated(limit, offset, filters: params); return page; } @@ -60,27 +58,27 @@ class _PaginatedSOLineListState extends PaginatedSearchState SoLineDetailWidget(item)) - ); - } - ); + title: Text(part.name), + subtitle: Text(part.description), + leading: InvenTreeAPI().getThumbnail(part.thumbnail), + trailing: Text(item.progressString, + style: TextStyle( + color: item.isComplete ? COLOR_SUCCESS : COLOR_WARNING)), + onTap: () async { + showLoadingOverlay(); + await item.reload(); + hideLoadingOverlay(); + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => SoLineDetailWidget(item))); + }); } else { return ListTile( title: Text(L10().error), - subtitle: Text("Missing part detail", style: TextStyle(color: COLOR_DANGER)), + subtitle: + Text("Missing part detail", style: TextStyle(color: COLOR_DANGER)), ); } } - } diff --git a/lib/widget/order/so_shipment_list.dart b/lib/widget/order/so_shipment_list.dart index a1e0dc74..0e804a3f 100644 --- a/lib/widget/order/so_shipment_list.dart +++ b/lib/widget/order/so_shipment_list.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; import "package:inventree/app_colors.dart"; @@ -9,19 +8,19 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/l10.dart"; class PaginatedSOShipmentList extends PaginatedSearchWidget { - - const PaginatedSOShipmentList(Map filters) : super(filters: filters); + const PaginatedSOShipmentList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().shipments; @override - _PaginatedSOShipmentListState createState() => _PaginatedSOShipmentListState(); + _PaginatedSOShipmentListState createState() => + _PaginatedSOShipmentListState(); } - -class _PaginatedSOShipmentListState extends PaginatedSearchState { - +class _PaginatedSOShipmentListState + extends PaginatedSearchState { _PaginatedSOShipmentListState() : super(); @override @@ -34,22 +33,23 @@ class _PaginatedSOShipmentListState extends PaginatedSearchState> get filterOptions => {}; @override - Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreeSalesOrderShipment().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreeSalesOrderShipment() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeSalesOrderShipment shipment = model as InvenTreeSalesOrderShipment; return ListTile( - title: Text(shipment.reference), - subtitle: Text(shipment.tracking_number), - leading: shipment.shipped ? Icon(TablerIcons.calendar_check, color: COLOR_SUCCESS) : Icon(TablerIcons.calendar_cancel, color: COLOR_WARNING), - trailing: shipment.shipped ? Text(shipment.shipment_date ?? "") : null - ); - + title: Text(shipment.reference), + subtitle: Text(shipment.tracking_number), + leading: shipment.shipped + ? Icon(TablerIcons.calendar_check, color: COLOR_SUCCESS) + : Icon(TablerIcons.calendar_cancel, color: COLOR_WARNING), + trailing: shipment.shipped ? Text(shipment.shipment_date ?? "") : null); } -} \ No newline at end of file +} diff --git a/lib/widget/paginator.dart b/lib/widget/paginator.dart index 01507270..11568f82 100644 --- a/lib/widget/paginator.dart +++ b/lib/widget/paginator.dart @@ -15,12 +15,10 @@ import "package:inventree/preferences.dart"; import "package:inventree/widget/refreshable_state.dart"; - /* * Abstract base widget class for rendering a PaginatedSearchState */ abstract class PaginatedSearchWidget extends StatefulWidget { - const PaginatedSearchWidget({this.filters = const {}, this.title = ""}); final String title; @@ -30,12 +28,11 @@ abstract class PaginatedSearchWidget extends StatefulWidget { final Map filters; } - /* * Generic stateful widget for displaying paginated data retrieved via the API */ -abstract class PaginatedSearchState extends State with BaseWidgetProperties { - +abstract class PaginatedSearchState + extends State with BaseWidgetProperties { static const _pageSize = 25; bool showSearchWidget = false; @@ -73,7 +70,6 @@ abstract class PaginatedSearchState extends Sta // Construct the boolean filter options for this list Future> constructFilters() async { - Map f = {}; for (String k in filterOptions.keys) { @@ -95,7 +91,8 @@ abstract class PaginatedSearchState extends Sta // Return the selected ordering "field" for this list widget Future orderingField() async { - dynamic field = await InvenTreeSettingsManager().getValue("${prefix}ordering_field", null); + dynamic field = await InvenTreeSettingsManager() + .getValue("${prefix}ordering_field", null); if (field != null && orderingOptions.containsKey(field.toString())) { // A valid ordering field has been found @@ -110,7 +107,8 @@ abstract class PaginatedSearchState extends Sta // Return the selected ordering "order" ("+" or "-") for this list widget Future orderingOrder() async { - dynamic order = await InvenTreeSettingsManager().getValue("${prefix}ordering_order", "+"); + dynamic order = await InvenTreeSettingsManager() + .getValue("${prefix}ordering_order", "+"); return order == "+" ? "+" : "-"; } @@ -137,10 +135,8 @@ abstract class PaginatedSearchState extends Sta // Construct the 'ordering' options List> _opts = []; - orderingOptions.forEach((k, v) => _opts.add({ - "value": k.toString(), - "display_name": v.toString() - })); + orderingOptions.forEach((k, v) => + _opts.add({"value": k.toString(), "display_name": v.toString()})); if (_field == null && _opts.isNotEmpty) { _field = _opts.first["value"]; @@ -161,12 +157,12 @@ abstract class PaginatedSearchState extends Sta "value": _order, "choices": [ { - "value": "+", - "display_name": "Ascending", + "value": "+", + "display_name": "Ascending", }, { - "value": "-", - "display_name": "Descending", + "value": "-", + "display_name": "Descending", } ] } @@ -212,31 +208,25 @@ abstract class PaginatedSearchState extends Sta } // Launch an interactive form for the user to select options - launchApiForm( - context, - L10().filteringOptions, - "", - fields, - icon: TablerIcons.circle_check, - onSuccess: (Map data) async { + launchApiForm(context, L10().filteringOptions, "", fields, + icon: TablerIcons.circle_check, + onSuccess: (Map data) async { + // Extract data from the processed form + String f = (data["ordering_field"] ?? _field) as String; + String o = (data["ordering_order"] ?? _order) as String; - // Extract data from the processed form - String f = (data["ordering_field"] ?? _field) as String; - String o = (data["ordering_order"] ?? _order) as String; + // Save values to settings + await InvenTreeSettingsManager().setValue("${prefix}ordering_field", f); + await InvenTreeSettingsManager().setValue("${prefix}ordering_order", o); - // Save values to settings - await InvenTreeSettingsManager().setValue("${prefix}ordering_field", f); - await InvenTreeSettingsManager().setValue("${prefix}ordering_order", o); - - // Save boolean fields - for (String key in filterOptions.keys) { - await setFilterValue(key, data[key]); - } - - // Refresh data from the server - _pagingController.refresh(); + // Save boolean fields + for (String key in filterOptions.keys) { + await setFilterValue(key, data[key]); } - ); + + // Refresh data from the server + _pagingController.refresh(); + }); } // Search query term @@ -245,7 +235,6 @@ abstract class PaginatedSearchState extends Sta int resultCount = 0; String resultsString() { - if (resultCount <= 0) { return noResultsText; } else { @@ -260,7 +249,8 @@ abstract class PaginatedSearchState extends Sta Timer? _debounceTimer; // Pagination controller - final PagingController _pagingController = PagingController(firstPageKey: 0); + final PagingController _pagingController = + PagingController(firstPageKey: 0); void refresh() { _pagingController.refresh(); @@ -286,8 +276,8 @@ abstract class PaginatedSearchState extends Sta * Each implementing class must override this function, * and return an InvenTreePageResponse object with the correct data format */ - Future requestPage(int limit, int offset, Map params) async { - + Future requestPage( + int limit, int offset, Map params) async { // Default implementation returns null - must be overridden return null; } @@ -301,7 +291,6 @@ abstract class PaginatedSearchState extends Sta // Include user search term if (searchTerm.isNotEmpty) { - String _search = searchTerm; // Include original search in search test @@ -329,11 +318,7 @@ abstract class PaginatedSearchState extends Sta params.addAll(f); } - final page = await requestPage( - _pageSize, - pageKey, - params - ); + final page = await requestPage(_pageSize, pageKey, params); // We may have disposed of the widget while the request was in progress // If this is the case, abort @@ -350,7 +335,7 @@ abstract class PaginatedSearchState extends Sta if (page != null) { for (var result in page.results) { - items.add(result); + items.add(result); } } @@ -369,14 +354,14 @@ abstract class PaginatedSearchState extends Sta sentryReportError( "paginator.fetchPage", - error, stackTrace, + error, + stackTrace, ); } } // Callback function when the search term is updated void updateSearchTerm() { - if (searchTerm == searchController.text) { // No change return; @@ -410,7 +395,6 @@ abstract class PaginatedSearchState extends Sta // Function to construct a single paginated item // Must be overridden in an implementing subclass Widget buildItem(BuildContext context, InvenTreeModel item) { - // This method must be overridden by the child class return ListTile( title: Text("*** UNIMPLEMENTED ***"), @@ -423,8 +407,7 @@ abstract class PaginatedSearchState extends Sta String get noResultsText => L10().noResults; @override - Widget build (BuildContext context) { - + Widget build(BuildContext context) { List children = [ buildTitleWidget(context), Divider(), @@ -434,29 +417,23 @@ abstract class PaginatedSearchState extends Sta children.add(buildSearchInput(context)); } - children.add( - Expanded( - child: CustomScrollView( - shrinkWrap: true, - physics: AlwaysScrollableScrollPhysics(), - scrollDirection: Axis.vertical, - slivers: [ - PagedSliverList.separated( - pagingController: _pagingController, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (ctx, item, index) { - return buildItem(ctx, item); - }, - noItemsFoundIndicatorBuilder: (context) { - return NoResultsWidget(noResultsText); - } - ), - separatorBuilder: (context, item) => const Divider(height: 1), - ) - ] + children.add(Expanded( + child: CustomScrollView( + shrinkWrap: true, + physics: AlwaysScrollableScrollPhysics(), + scrollDirection: Axis.vertical, + slivers: [ + PagedSliverList.separated( + pagingController: _pagingController, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (ctx, item, index) { + return buildItem(ctx, item); + }, noItemsFoundIndicatorBuilder: (context) { + return NoResultsWidget(noResultsText); + }), + separatorBuilder: (context, item) => const Divider(height: 1), ) - ) - ); + ]))); return RefreshIndicator( child: Column( @@ -473,18 +450,16 @@ abstract class PaginatedSearchState extends Sta * Build the title widget for this list */ Widget buildTitleWidget(BuildContext context) { - const double icon_size = 32; List _icons = []; if (filterOptions.isNotEmpty || orderingOptions.isNotEmpty) { _icons.add(IconButton( - onPressed: () async { - _setOrderingOptions(context); - }, - icon: Icon(Icons.filter_alt, size: icon_size) - )); + onPressed: () async { + _setOrderingOptions(context); + }, + icon: Icon(Icons.filter_alt, size: icon_size))); } _icons.add(IconButton( @@ -493,8 +468,8 @@ abstract class PaginatedSearchState extends Sta showSearchWidget = !showSearchWidget; }); }, - icon: Icon(showSearchWidget ? Icons.zoom_out : Icons.search, size: icon_size) - )); + icon: Icon(showSearchWidget ? Icons.zoom_out : Icons.search, + size: icon_size))); // _icons.add(IconButton( // onPressed: () async { @@ -512,9 +487,7 @@ abstract class PaginatedSearchState extends Sta ), subtitle: Text( "${L10().results}: ${resultCount}", - style: TextStyle( - fontStyle: FontStyle.italic - ), + style: TextStyle(fontStyle: FontStyle.italic), ), trailing: Row( mainAxisSize: MainAxisSize.min, @@ -528,41 +501,40 @@ abstract class PaginatedSearchState extends Sta */ Widget buildSearchInput(BuildContext context) { return ListTile( - trailing: GestureDetector( - child: Icon( - searchController.text.isEmpty ? TablerIcons.search : TablerIcons.backspace, - color: searchController.text.isNotEmpty ? COLOR_DANGER : COLOR_ACTION, + trailing: GestureDetector( + child: Icon( + searchController.text.isEmpty + ? TablerIcons.search + : TablerIcons.backspace, + color: + searchController.text.isNotEmpty ? COLOR_DANGER : COLOR_ACTION, + ), + onTap: () { + if (searchController.text.isNotEmpty) { + searchController.clear(); + } + updateSearchTerm(); + }, ), - onTap: () { - if (searchController.text.isNotEmpty) { - searchController.clear(); - } - updateSearchTerm(); - }, - ), - title: TextFormField( - controller: searchController, - onChanged: (value) { - updateSearchTerm(); - }, - decoration: InputDecoration( - hintText: L10().search, - ), - ) - ); + title: TextFormField( + controller: searchController, + onChanged: (value) { + updateSearchTerm(); + }, + decoration: InputDecoration( + hintText: L10().search, + ), + )); } } - class NoResultsWidget extends StatelessWidget { - const NoResultsWidget(this.description); final String description; @override Widget build(BuildContext context) { - return ListTile( title: Text( description, @@ -571,5 +543,4 @@ class NoResultsWidget extends StatelessWidget { leading: Icon(TablerIcons.exclamation_circle, color: COLOR_WARNING), ); } - } diff --git a/lib/widget/part/bom_list.dart b/lib/widget/part/bom_list.dart index 77a169be..22df1391 100644 --- a/lib/widget/part/bom_list.dart +++ b/lib/widget/part/bom_list.dart @@ -1,4 +1,3 @@ - import "package:flutter/material.dart"; import "package:flutter_tabler_icons/flutter_tabler_icons.dart"; @@ -14,13 +13,13 @@ import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; - /* * Widget for displaying a Bill of Materials for a specified Part instance */ class BillOfMaterialsWidget extends StatefulWidget { - - const BillOfMaterialsWidget(this.part, {this.isParentComponent = true, Key? key}) : super(key: key); + const BillOfMaterialsWidget(this.part, + {this.isParentComponent = true, Key? key}) + : super(key: key); final InvenTreePart part; @@ -46,19 +45,18 @@ class _BillOfMaterialsState extends RefreshableState { @override List appBarActions(BuildContext context) => [ - IconButton( - icon: Icon(TablerIcons.filter), - onPressed: () async { - setState(() { - showFilterOptions = !showFilterOptions; - }); - }, - ) - ]; + IconButton( + icon: Icon(TablerIcons.filter), + onPressed: () async { + setState(() { + showFilterOptions = !showFilterOptions; + }); + }, + ) + ]; @override Widget getBody(BuildContext context) { - Map filters = {}; if (widget.isParentComponent) { @@ -72,7 +70,9 @@ class _BillOfMaterialsState extends RefreshableState { ListTile( leading: InvenTreeAPI().getThumbnail(widget.part.thumbnail), title: Text(widget.part.fullname), - subtitle: Text(widget.isParentComponent ? L10().billOfMaterials : L10().usedInDetails), + subtitle: Text(widget.isParentComponent + ? L10().billOfMaterials + : L10().usedInDetails), trailing: Text(L10().quantity), ), Divider(thickness: 1.25), @@ -87,13 +87,13 @@ class _BillOfMaterialsState extends RefreshableState { } } - /* * Create a paginated widget displaying a list of BomItem objects */ class PaginatedBomList extends PaginatedSearchWidget { - - const PaginatedBomList(Map filters, {this.isParentPart = true}) : super(filters: filters); + const PaginatedBomList(Map filters, + {this.isParentPart = true}) + : super(filters: filters); final bool isParentPart; @@ -104,9 +104,7 @@ class PaginatedBomList extends PaginatedSearchWidget { _PaginatedBomListState createState() => _PaginatedBomListState(); } - class _PaginatedBomListState extends PaginatedSearchState { - _PaginatedBomListState() : super(); @override @@ -114,32 +112,33 @@ class _PaginatedBomListState extends PaginatedSearchState { @override Map get orderingOptions => { - "quantity": L10().quantity, - "sub_part": L10().part, - }; + "quantity": L10().quantity, + "sub_part": L10().part, + }; @override Map> get filterOptions => { - "sub_part_assembly": { - "label": L10().filterAssembly, - "help_text": L10().filterAssemblyDetail, - } - }; + "sub_part_assembly": { + "label": L10().filterAssembly, + "help_text": L10().filterAssemblyDetail, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreeBomItem().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = + await InvenTreeBomItem().listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeBomItem bomItem = model as InvenTreeBomItem; - InvenTreePart? subPart = widget.isParentPart ? bomItem.subPart : bomItem.part; + InvenTreePart? subPart = + widget.isParentPart ? bomItem.subPart : bomItem.part; String title = subPart?.fullname ?? "error - no name"; @@ -151,16 +150,17 @@ class _PaginatedBomListState extends PaginatedSearchState { style: TextStyle(fontWeight: FontWeight.bold), ), leading: InvenTreeAPI().getThumbnail(subPart?.thumbnail ?? ""), - onTap: subPart == null ? null : () async { + onTap: subPart == null + ? null + : () async { + showLoadingOverlay(); + var part = await InvenTreePart().get(subPart.pk); + hideLoadingOverlay(); - showLoadingOverlay(); - var part = await InvenTreePart().get(subPart.pk); - hideLoadingOverlay(); - - if (part is InvenTreePart) { - part.goToDetailPage(context); - } - }, + if (part is InvenTreePart) { + part.goToDetailPage(context); + } + }, ); } -} \ No newline at end of file +} diff --git a/lib/widget/part/category_display.dart b/lib/widget/part/category_display.dart index 7ca833ee..0960a42a 100644 --- a/lib/widget/part/category_display.dart +++ b/lib/widget/part/category_display.dart @@ -13,9 +13,7 @@ import "package:inventree/widget/progress.dart"; import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/refreshable_state.dart"; - class CategoryDisplayWidget extends StatefulWidget { - const CategoryDisplayWidget(this.category, {Key? key}) : super(key: key); final InvenTreePartCategory? category; @@ -24,9 +22,7 @@ class CategoryDisplayWidget extends StatefulWidget { _CategoryDisplayState createState() => _CategoryDisplayState(); } - class _CategoryDisplayState extends RefreshableState { - _CategoryDisplayState(); @override @@ -38,15 +34,13 @@ class _CategoryDisplayState extends RefreshableState { if (widget.category != null) { if (InvenTreePartCategory().canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().editCategory, - onPressed: () { - _editCategoryDialog(context); - }, - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().editCategory, + onPressed: () { + _editCategoryDialog(context); + }, + )); } } @@ -58,25 +52,20 @@ class _CategoryDisplayState extends RefreshableState { List actions = []; if (InvenTreePart().canCreate) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.box), - label: L10().partCreateDetail, - onTap: _newPart, - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.box), + label: L10().partCreateDetail, + onTap: _newPart, + )); } if (InvenTreePartCategory().canCreate) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.sitemap), label: L10().categoryCreateDetail, onTap: () { _newCategory(context); - } - ) - ); + })); } return actions; @@ -90,14 +79,10 @@ class _CategoryDisplayState extends RefreshableState { return; } - _cat.editForm( - context, - L10().editCategory, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().categoryUpdated, success: true); - } - ); + _cat.editForm(context, L10().editCategory, onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().categoryUpdated, success: true); + }); } @override @@ -107,7 +92,6 @@ class _CategoryDisplayState extends RefreshableState { @override Future request(BuildContext context) async { - // Update the category if (widget.category != null) { final bool result = await widget.category?.reload() ?? false; @@ -121,67 +105,60 @@ class _CategoryDisplayState extends RefreshableState { Widget getCategoryDescriptionCard({bool extra = true}) { if (widget.category == null) { return Card( - child: ListTile( - leading: Icon(TablerIcons.packages), - title: Text( - L10().partCategoryTopLevel, - style: TextStyle(fontStyle: FontStyle.italic), - ) - ) - ); + child: ListTile( + leading: Icon(TablerIcons.packages), + title: Text( + L10().partCategoryTopLevel, + style: TextStyle(fontStyle: FontStyle.italic), + ))); } else { - List children = [ ListTile( - title: Text("${widget.category?.name}", - style: TextStyle(fontWeight: FontWeight.bold) - ), - subtitle: Text("${widget.category?.description}"), - leading: widget.category!.customIcon != null ? Icon(widget.category!.customIcon) : Icon(TablerIcons.sitemap) - ), + title: Text("${widget.category?.name}", + style: TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text("${widget.category?.description}"), + leading: widget.category!.customIcon != null + ? Icon(widget.category!.customIcon) + : Icon(TablerIcons.sitemap)), ]; if (extra) { - children.add( - ListTile( - title: Text(L10().parentCategory), - subtitle: Text("${widget.category?.parentPathString}"), - leading: Icon( - TablerIcons.arrow_move_up, - color: COLOR_ACTION, - ), - onTap: () async { + children.add(ListTile( + title: Text(L10().parentCategory), + subtitle: Text("${widget.category?.parentPathString}"), + leading: Icon( + TablerIcons.arrow_move_up, + color: COLOR_ACTION, + ), + onTap: () async { + int parentId = widget.category?.parentId ?? -1; - int parentId = widget.category?.parentId ?? -1; + if (parentId < 0) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => CategoryDisplayWidget(null))); + } else { + showLoadingOverlay(); + var cat = await InvenTreePartCategory().get(parentId); + hideLoadingOverlay(); - if (parentId < 0) { - Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))); - } else { - - showLoadingOverlay(); - var cat = await InvenTreePartCategory().get(parentId); - hideLoadingOverlay(); - - if (cat is InvenTreePartCategory) { - cat.goToDetailPage(context); - } - } - }, - ) - ); + if (cat is InvenTreePartCategory) { + cat.goToDetailPage(context); + } + } + }, + )); } return Card( - child: Column( - children: children - ), + child: Column(children: children), ); } } @override List getTabIcons(BuildContext context) { - return [ Tab(text: L10().details), Tab(text: L10().parts), @@ -198,7 +175,6 @@ class _CategoryDisplayState extends RefreshableState { // Construct the "details" panel List detailTiles() { - Map filters = {}; int? parent = widget.category?.pk; @@ -225,7 +201,6 @@ class _CategoryDisplayState extends RefreshableState { // Construct the "parts" panel List partsTiles() { - Map filters = { "category": widget.category?.pk.toString() ?? "null", }; @@ -239,50 +214,35 @@ class _CategoryDisplayState extends RefreshableState { } Future _newCategory(BuildContext context) async { - int pk = widget.category?.pk ?? -1; - InvenTreePartCategory().createForm( - context, - L10().categoryCreate, - data: { - "parent": (pk > 0) ? pk : null, - }, - onSuccess: (result) async { + InvenTreePartCategory().createForm(context, L10().categoryCreate, data: { + "parent": (pk > 0) ? pk : null, + }, onSuccess: (result) async { + Map data = result as Map; - Map data = result as Map; - - if (data.containsKey("pk")) { - var cat = InvenTreePartCategory.fromJson(data); - cat.goToDetailPage(context).then((_) { - refresh(context); - }); - } else { + if (data.containsKey("pk")) { + var cat = InvenTreePartCategory.fromJson(data); + cat.goToDetailPage(context).then((_) { refresh(context); - } + }); + } else { + refresh(context); } - ); + }); } Future _newPart() async { - int pk = widget.category?.pk ?? -1; - InvenTreePart().createForm( - context, - L10().partCreate, - data: { - "category": (pk > 0) ? pk : null - }, - onSuccess: (result) async { + InvenTreePart().createForm(context, L10().partCreate, + data: {"category": (pk > 0) ? pk : null}, onSuccess: (result) async { + Map data = result as Map; - Map data = result as Map; - - if (data.containsKey("pk")) { - var part = InvenTreePart.fromJson(data); - part.goToDetailPage(context); - } + if (data.containsKey("pk")) { + var part = InvenTreePart.fromJson(data); + part.goToDetailPage(context); } - ); + }); } } diff --git a/lib/widget/part/category_list.dart b/lib/widget/part/category_list.dart index c0aaa865..a3905ba2 100644 --- a/lib/widget/part/category_list.dart +++ b/lib/widget/part/category_list.dart @@ -9,19 +9,15 @@ import "package:inventree/api.dart"; import "package:inventree/l10.dart"; class PartCategoryList extends StatefulWidget { - const PartCategoryList(this.filters); final Map filters; @override _PartCategoryListState createState() => _PartCategoryListState(); - } - class _PartCategoryListState extends RefreshableState { - _PartCategoryListState(); @override @@ -34,19 +30,20 @@ class _PartCategoryListState extends RefreshableState { } class PaginatedPartCategoryList extends PaginatedSearchWidget { - - const PaginatedPartCategoryList(Map filters, {String title = ""}) : super(filters: filters, title: title); + const PaginatedPartCategoryList(Map filters, + {String title = ""}) + : super(filters: filters, title: title); @override String get searchTitle => title.isNotEmpty ? title : L10().partCategories; @override - _PaginatedPartCategoryListState createState() => _PaginatedPartCategoryListState(); + _PaginatedPartCategoryListState createState() => + _PaginatedPartCategoryListState(); } - -class _PaginatedPartCategoryListState extends PaginatedSearchState { - +class _PaginatedPartCategoryListState + extends PaginatedSearchState { // _PaginatedPartCategoryListState(Map filters, bool searchEnabled) : super(filters, searchEnabled); @override @@ -54,17 +51,16 @@ class _PaginatedPartCategoryListState extends PaginatedSearchState> get filterOptions => { - "cascade": { - "default": false, - "label": L10().includeSubcategories, - "help_text": L10().includeSubcategoriesDetail, - "tristate": false, - } - }; + "cascade": { + "default": false, + "label": L10().includeSubcategories, + "help_text": L10().includeSubcategoriesDetail, + "tristate": false, + } + }; @override Map get orderingOptions { - Map options = { "name": L10().name, "level": L10().level, @@ -81,16 +77,16 @@ class _PaginatedPartCategoryListState extends PaginatedSearchState requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreePartCategory().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreePartCategory() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreePartCategory category = model as InvenTreePartCategory; return ListTile( @@ -103,4 +99,4 @@ class _PaginatedPartCategoryListState extends PaginatedSearchState _PartDisplayState(part); - } - class _PartDisplayState extends RefreshableState { - _PartDisplayState(this.part); InvenTreePart part; @@ -75,15 +70,12 @@ class _PartDisplayState extends RefreshableState { List actions = []; if (InvenTreePart().canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().editPart, - onPressed: () { - _editPartDialog(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().editPart, + onPressed: () { + _editPartDialog(context); + })); } return actions; } @@ -93,13 +85,8 @@ class _PartDisplayState extends RefreshableState { List actions = []; if (InvenTreePart().canEdit) { - actions.add( - customBarcodeAction( - context, this, - widget.part.customBarcode, "part", - widget.part.pk - ) - ); + actions.add(customBarcodeAction( + context, this, widget.part.customBarcode, "part", widget.part.pk)); } return actions; @@ -110,33 +97,22 @@ class _PartDisplayState extends RefreshableState { List actions = []; if (InvenTreeStockItem().canCreate) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.packages), - label: L10().stockItemCreate, - onTap: () { - _newStockItem(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.packages), + label: L10().stockItemCreate, + onTap: () { + _newStockItem(context); + })); } if (labels.isNotEmpty) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.printer), label: L10().printLabel, onTap: () async { - selectAndPrintLabel( - context, - labels, - widget.part.pk, - "part", - "part=${widget.part.pk}" - ); - } - ) - ); + selectAndPrintLabel(context, labels, widget.part.pk, "part", + "part=${widget.part.pk}"); + })); } return actions; @@ -153,14 +129,16 @@ class _PartDisplayState extends RefreshableState { @override Future request(BuildContext context) async { - final bool result = await part.reload(); // Load page settings from local storage - showPricing = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PRICING, true); - showParameters = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PARAMETERS, true); + showPricing = + await InvenTreeSettingsManager().getBool(INV_PART_SHOW_PRICING, true); + showParameters = await InvenTreeSettingsManager() + .getBool(INV_PART_SHOW_PARAMETERS, true); showBom = await InvenTreeSettingsManager().getBool(INV_PART_SHOW_BOM, true); - allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true); + allowLabelPrinting = await InvenTreeSettingsManager() + .getBool(INV_ENABLE_LABEL_PRINTING, true); if (!result || part.pk == -1) { // Part could not be loaded, for some reason @@ -211,11 +189,9 @@ class _PartDisplayState extends RefreshableState { } // Request the number of BOM items - InvenTreePart().count( - filters: { - "in_bom_for": part.pk.toString(), - } - ).then((int value) { + InvenTreePart().count(filters: { + "in_bom_for": part.pk.toString(), + }).then((int value) { if (mounted) { setState(() { bomCount = value; @@ -224,11 +200,9 @@ class _PartDisplayState extends RefreshableState { }); // Request number of "used in" parts - InvenTreeBomItem().count( - filters: { - "uses": part.pk.toString(), - } - ).then((int value) { + InvenTreeBomItem().count(filters: { + "uses": part.pk.toString(), + }).then((int value) { if (mounted) { setState(() { usedInCount = value; @@ -237,11 +211,9 @@ class _PartDisplayState extends RefreshableState { }); // Request the number of variant items - InvenTreePart().count( - filters: { - "variant_of": part.pk.toString(), - } - ).then((int value) { + InvenTreePart().count(filters: { + "variant_of": part.pk.toString(), + }).then((int value) { if (mounted) { setState(() { variantCount = value; @@ -253,16 +225,12 @@ class _PartDisplayState extends RefreshableState { allowLabelPrinting &= api.supportsMixin("labels"); if (allowLabelPrinting) { - - String model_type = api.supportsModernLabelPrinting ? InvenTreePart.MODEL_TYPE : "part"; + String model_type = + api.supportsModernLabelPrinting ? InvenTreePart.MODEL_TYPE : "part"; String item_key = api.supportsModernLabelPrinting ? "items" : "part"; _labels = await getLabelTemplates( - model_type, - { - item_key: widget.part.pk.toString() - } - ); + model_type, {item_key: widget.part.pk.toString()}); } if (mounted) { @@ -273,41 +241,33 @@ class _PartDisplayState extends RefreshableState { } void _editPartDialog(BuildContext context) { - - part.editForm( - context, - L10().editPart, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().partEdited, success: true); - } - ); + part.editForm(context, L10().editPart, onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().partEdited, success: true); + }); } Widget headerTile() { return Card( - child: ListTile( - title: Text(part.fullname), - subtitle: Text(part.description), - trailing: Text( - part.stockString(), + child: ListTile( + title: Text(part.fullname), + subtitle: Text(part.description), + trailing: Text(part.stockString(), style: TextStyle( fontSize: 20, - ) - ), - leading: GestureDetector( + )), + leading: GestureDetector( child: api.getImage(part.thumbnail), onTap: () { Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PartImageWidget(part) - ) - ).then((value) { + context, + MaterialPageRoute( + builder: (context) => PartImageWidget(part))) + .then((value) { refresh(context); }); }), - ), + ), ); } @@ -315,13 +275,10 @@ class _PartDisplayState extends RefreshableState { * Build a list of tiles to display under the part description */ List partTiles() { - List tiles = []; // Image / name / description - tiles.add( - headerTile() - ); + tiles.add(headerTile()); if (loading) { tiles.add(progressIndicator()); @@ -329,31 +286,16 @@ class _PartDisplayState extends RefreshableState { } if (!part.isActive) { - tiles.add( - ListTile( - title: Text( - L10().inactive, - style: TextStyle( - color: COLOR_DANGER - ) - ), - subtitle: Text( - L10().inactiveDetail, - style: TextStyle( - color: COLOR_DANGER - ) - ), - leading: Icon( - TablerIcons.exclamation_circle, - color: COLOR_DANGER - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().inactive, style: TextStyle(color: COLOR_DANGER)), + subtitle: + Text(L10().inactiveDetail, style: TextStyle(color: COLOR_DANGER)), + leading: Icon(TablerIcons.exclamation_circle, color: COLOR_DANGER), + )); } if (parentPart != null) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().templatePart), subtitle: Text(parentPart!.fullname), leading: api.getImage( @@ -363,68 +305,56 @@ class _PartDisplayState extends RefreshableState { ), onTap: () { parentPart?.goToDetailPage(context); - } - ) - ); + })); } // Category information if (part.categoryName.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().partCategory), - subtitle: Text("${part.categoryName}"), - leading: Icon(TablerIcons.sitemap, color: COLOR_ACTION), - onTap: () async { - if (part.categoryId > 0) { + tiles.add(ListTile( + title: Text(L10().partCategory), + subtitle: Text("${part.categoryName}"), + leading: Icon(TablerIcons.sitemap, color: COLOR_ACTION), + onTap: () async { + if (part.categoryId > 0) { + showLoadingOverlay(); + var cat = await InvenTreePartCategory().get(part.categoryId); + hideLoadingOverlay(); - showLoadingOverlay(); - var cat = await InvenTreePartCategory().get(part.categoryId); - hideLoadingOverlay(); - - if (cat is InvenTreePartCategory) { - cat.goToDetailPage(context); - } - } - }, - ) - ); + if (cat is InvenTreePartCategory) { + cat.goToDetailPage(context); + } + } + }, + )); } else { - tiles.add( - ListTile( - title: Text(L10().partCategory), - subtitle: Text(L10().partCategoryTopLevel), - leading: Icon(TablerIcons.sitemap, color: COLOR_ACTION), - onTap: () { - Navigator.push(context, MaterialPageRoute( + tiles.add(ListTile( + title: Text(L10().partCategory), + subtitle: Text(L10().partCategoryTopLevel), + leading: Icon(TablerIcons.sitemap, color: COLOR_ACTION), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( builder: (context) => CategoryDisplayWidget(null))); - }, - ) - ); + }, + )); } // Display number of "variant" parts if any exist if (variantCount > 0) { - tiles.add( - ListTile( - title: Text(L10().variants), - leading: Icon(TablerIcons.versions, color: COLOR_ACTION), - trailing: Text(variantCount.toString()), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => PartList( - { - "variant_of": part.pk.toString(), - }, - title: L10().variants - ) - ) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().variants), + leading: Icon(TablerIcons.versions, color: COLOR_ACTION), + trailing: Text(variantCount.toString()), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PartList({ + "variant_of": part.pk.toString(), + }, title: L10().variants))); + }, + )); } tiles.add( @@ -442,12 +372,9 @@ class _PartDisplayState extends RefreshableState { ); if (showPricing && partPricing != null) { - String pricing = formatPriceRange( - partPricing?.overallMin, - partPricing?.overallMax, - currency: partPricing?.currency - ); + partPricing?.overallMin, partPricing?.overallMax, + currency: partPricing?.currency); tiles.add( ListTile( @@ -463,7 +390,8 @@ class _PartDisplayState extends RefreshableState { Navigator.push( context, MaterialPageRoute( - builder: (context) => PartPricingWidget(part: part, partPricing: partPricing), + builder: (context) => + PartPricingWidget(part: part, partPricing: partPricing), ), ); }, @@ -473,173 +401,141 @@ 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: Text("${part.onOrderString}"), - onTap: () { - // TODO - Order views - }, - ) - ); - + tiles.add(ListTile( + title: Text(L10().onOrder), + subtitle: Text(L10().onOrderDetails), + leading: Icon(TablerIcons.shopping_cart), + trailing: Text("${part.onOrderString}"), + onTap: () { + // TODO - Order views + }, + )); } // Tiles for an "assembly" part if (part.isAssembly) { - if (showBom && bomCount > 0) { - tiles.add( - ListTile( - title: Text(L10().billOfMaterials), - leading: Icon(TablerIcons.list_tree, color: COLOR_ACTION), - trailing: Text(bomCount.toString()), - onTap: () { - Navigator.push(context, MaterialPageRoute( - builder: (context) => BillOfMaterialsWidget(part, isParentComponent: true) - )); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().billOfMaterials), + leading: Icon(TablerIcons.list_tree, color: COLOR_ACTION), + trailing: Text(bomCount.toString()), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + BillOfMaterialsWidget(part, isParentComponent: true))); + }, + )); } if (part.building > 0) { - tiles.add( - ListTile( - title: Text(L10().building), - leading: Icon(TablerIcons.tools), - trailing: Text("${simpleNumberString(part.building)}"), - onTap: () { - // TODO - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().building), + leading: Icon(TablerIcons.tools), + trailing: Text("${simpleNumberString(part.building)}"), + onTap: () { + // TODO + }, + )); } } if (part.isComponent) { if (showBom && usedInCount > 0) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().usedIn), subtitle: Text(L10().usedInDetails), leading: Icon(TablerIcons.stack_2, color: COLOR_ACTION), trailing: Text(usedInCount.toString()), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => BillOfMaterialsWidget(part, isParentComponent: false) - ) - ); - } - ) - ); + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BillOfMaterialsWidget(part, + isParentComponent: false))); + })); } } // Keywords? if (part.keywords.isNotEmpty) { - tiles.add( - ListTile( - title: Text("${part.keywords}"), - leading: Icon(TablerIcons.tags), - ) - ); + tiles.add(ListTile( + title: Text("${part.keywords}"), + leading: Icon(TablerIcons.tags), + )); } // External link? if (part.link.isNotEmpty) { - tiles.add( - ListTile( - title: Text("${part.link}"), - leading: Icon(TablerIcons.link, color: COLOR_ACTION), - onTap: () { - part.openLink(); - }, - ) - ); + tiles.add(ListTile( + title: Text("${part.link}"), + leading: Icon(TablerIcons.link, color: COLOR_ACTION), + onTap: () { + part.openLink(); + }, + )); } // Tiles for "component" part if (part.isComponent && part.usedInCount > 0) { - - tiles.add( - ListTile( - title: Text(L10().usedIn), - subtitle: Text(L10().usedInDetails), - leading: Icon(TablerIcons.sitemap), - trailing: Text("${part.usedInCount}"), - onTap: () { - // TODO - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().usedIn), + subtitle: Text(L10().usedInDetails), + leading: Icon(TablerIcons.sitemap), + trailing: Text("${part.usedInCount}"), + onTap: () { + // TODO + }, + )); } if (part.isPurchaseable) { - if (part.supplierCount > 0) { - tiles.add( - ListTile( - title: Text(L10().suppliers), - leading: Icon(TablerIcons.building_factory, color: COLOR_ACTION), - trailing: Text("${part.supplierCount}"), - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => SupplierPartList({ - "part": part.pk.toString() - })) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().suppliers), + leading: Icon(TablerIcons.building_factory, color: COLOR_ACTION), + trailing: Text("${part.supplierCount}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + SupplierPartList({"part": part.pk.toString()}))); + }, + )); } } // Notes field - tiles.add( - ListTile( - title: Text(L10().notes), - leading: Icon(TablerIcons.note, color: COLOR_ACTION), - trailing: Text(""), - onTap: () { - Navigator.push( - context, - MaterialPageRoute(builder: (context) => NotesWidget(part)) - ); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().notes), + leading: Icon(TablerIcons.note, color: COLOR_ACTION), + trailing: Text(""), + onTap: () { + Navigator.push(context, + MaterialPageRoute(builder: (context) => NotesWidget(part))); + }, + )); - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, - onTap: () { - Navigator.push( + tiles.add(ListTile( + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => AttachmentWidget( - InvenTreePartAttachment(), - part.pk, - L10().part, - part.canEdit - ) - ) - ); - }, - ) - ); + builder: (context) => AttachmentWidget( + InvenTreePartAttachment(), + part.pk, + L10().part, + part.canEdit))); + }, + )); return tiles; - } // Return tiles for each stock item @@ -648,16 +544,16 @@ class _PartDisplayState extends RefreshableState { tiles.add(headerTile()); - tiles.add( - ListTile( - title: Text( - L10().stockItems, - style: TextStyle(fontWeight: FontWeight.bold), - ), - subtitle: part.stockItems.isEmpty ? Text(L10().stockItemsNotAvailable) : null, - trailing: part.stockItems.isNotEmpty ? Text("${part.stockItems.length}") : null, - ) - ); + tiles.add(ListTile( + title: Text( + L10().stockItems, + style: TextStyle(fontWeight: FontWeight.bold), + ), + subtitle: + part.stockItems.isEmpty ? Text(L10().stockItemsNotAvailable) : null, + trailing: + part.stockItems.isNotEmpty ? Text("${part.stockItems.length}") : null, + )); return tiles; } @@ -666,7 +562,6 @@ class _PartDisplayState extends RefreshableState { * Launch a form to create a new StockItem for this part */ Future _newStockItem(BuildContext context) async { - var fields = InvenTreeStockItem().formFields(); // Serial number cannot be directly edited here @@ -677,9 +572,7 @@ class _PartDisplayState extends RefreshableState { int? default_location = part.defaultLocation; - Map data = { - "part": part.pk.toString() - }; + Map data = {"part": part.pk.toString()}; if (default_location != null) { data["location"] = default_location; @@ -688,15 +581,18 @@ class _PartDisplayState extends RefreshableState { if (part.isTrackable) { // read the next available serial number showLoadingOverlay(); - var response = await api.get("/api/part/${part.pk}/serial-numbers/", expectedStatusCode: null); + var response = await api.get("/api/part/${part.pk}/serial-numbers/", + expectedStatusCode: null); hideLoadingOverlay(); if (response.isValid() && response.statusCode == 200) { - data["serial_numbers"] = response.data["next"] ?? response.data["latest"]; + data["serial_numbers"] = + response.data["next"] ?? response.data["latest"]; } - print("response: " + response.statusCode.toString() + response.data.toString()); - + print("response: " + + response.statusCode.toString() + + response.data.toString()); } else { // Cannot set serial numbers for non-trackable parts fields.remove("serial_numbers"); @@ -704,29 +600,20 @@ class _PartDisplayState extends RefreshableState { print("data: ${data.toString()}"); - InvenTreeStockItem().createForm( - context, - L10().stockItemCreate, - fields: fields, - data: data, - onSuccess: (result) async { + InvenTreeStockItem().createForm(context, L10().stockItemCreate, + fields: fields, data: data, onSuccess: (result) async { + Map data = result as Map; - Map data = result as Map; - - if (data.containsKey("pk")) { - var item = InvenTreeStockItem.fromJson(data); - item.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var item = InvenTreeStockItem.fromJson(data); + item.goToDetailPage(context); + } + }); } @override List getTabIcons(BuildContext context) { - List icons = [ - Tab(text: L10().details), - Tab(text: L10().stock) - ]; + List icons = [Tab(text: L10().details), Tab(text: L10().stock)]; if (showParameters) { icons.add(Tab(text: L10().parameters)); @@ -739,11 +626,10 @@ class _PartDisplayState extends RefreshableState { List getTabs(BuildContext context) { List tabs = [ SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: Column( - children: partTiles(), - ) - ), + physics: AlwaysScrollableScrollPhysics(), + child: Column( + children: partTiles(), + )), PaginatedStockItemList({"part": part.pk.toString()}) ]; @@ -753,5 +639,4 @@ class _PartDisplayState extends RefreshableState { return tabs; } - } diff --git a/lib/widget/part/part_image_widget.dart b/lib/widget/part/part_image_widget.dart index 41da3714..1d8817c0 100644 --- a/lib/widget/part/part_image_widget.dart +++ b/lib/widget/part/part_image_widget.dart @@ -11,19 +11,15 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/l10.dart"; class PartImageWidget extends StatefulWidget { - const PartImageWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; @override _PartImageState createState() => _PartImageState(part); - } - class _PartImageState extends RefreshableState { - _PartImageState(this.part); final InvenTreePart part; @@ -38,32 +34,24 @@ class _PartImageState extends RefreshableState { @override List appBarActions(BuildContext context) { - List actions = []; if (part.canEdit) { - // File upload - actions.add( - IconButton( - icon: Icon(TablerIcons.file_upload), - onPressed: () async { + actions.add(IconButton( + icon: Icon(TablerIcons.file_upload), + onPressed: () async { + FilePickerDialog.pickFile(onPicked: (File file) async { + final result = await part.uploadImage(file); - FilePickerDialog.pickFile( - onPicked: (File file) async { - final result = await part.uploadImage(file); + if (!result) { + showSnackIcon(L10().uploadFailed, success: false); + } - if (!result) { - showSnackIcon(L10().uploadFailed, success: false); - } - - refresh(context); - } - ); - - }, - ) - ); + refresh(context); + }); + }, + )); } return actions; @@ -73,5 +61,4 @@ class _PartImageState extends RefreshableState { Widget getBody(BuildContext context) { return InvenTreeAPI().getImage(part.image); } - -} \ No newline at end of file +} diff --git a/lib/widget/part/part_list.dart b/lib/widget/part/part_list.dart index 8433e0b7..c94bb4d3 100644 --- a/lib/widget/part/part_list.dart +++ b/lib/widget/part/part_list.dart @@ -9,9 +9,7 @@ import "package:inventree/inventree/part.dart"; import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; - class PartList extends StatefulWidget { - const PartList(this.filters, {this.title = ""}); final String title; @@ -22,9 +20,7 @@ class PartList extends StatefulWidget { _PartListState createState() => _PartListState(filters, title); } - class _PartListState extends RefreshableState { - _PartListState(this.filters, this.title); final String title; @@ -40,13 +36,11 @@ class _PartListState extends RefreshableState { Widget getBody(BuildContext context) { return PaginatedPartList(filters); } - } - class PaginatedPartList extends PaginatedSearchWidget { - - const PaginatedPartList(Map filters) : super(filters: filters); + const PaginatedPartList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().parts; @@ -55,9 +49,7 @@ class PaginatedPartList extends PaginatedSearchWidget { _PaginatedPartListState createState() => _PaginatedPartListState(); } - class _PaginatedPartListState extends PaginatedSearchState { - _PaginatedPartListState() : super(); @override @@ -65,74 +57,70 @@ class _PaginatedPartListState extends PaginatedSearchState { @override Map get orderingOptions => { - "name": L10().name, - "in_stock": L10().stock, - "IPN": L10().internalPartNumber, - }; + "name": L10().name, + "in_stock": L10().stock, + "IPN": L10().internalPartNumber, + }; @override Map> get filterOptions => { - "cascade": { - "default": true, - "label": L10().includeSubcategories, - "help_text": L10().includeSubcategoriesDetail, - }, - "active": { - "label": L10().filterActive, - "help_text": L10().filterActiveDetail, - "tristate": true, - }, - "assembly": { - "label": L10().filterAssembly, - "help_text": L10().filterAssemblyDetail - }, - "component": { - "label": L10().filterComponent, - "help_text": L10().filterComponentDetail, - }, - "is_template": { - "label": L10().filterTemplate, - "help_text": L10().filterTemplateDetail - }, - "trackable": { - "label": L10().filterTrackable, - "help_text": L10().filterTrackableDetail, - }, - "virtual": { - "label": L10().filterVirtual, - "help_text": L10().filterVirtualDetail, - }, - "has_stock": { - "label": L10().filterInStock, - "help_text": L10().filterInStockDetail, - } - }; + "cascade": { + "default": true, + "label": L10().includeSubcategories, + "help_text": L10().includeSubcategoriesDetail, + }, + "active": { + "label": L10().filterActive, + "help_text": L10().filterActiveDetail, + "tristate": true, + }, + "assembly": { + "label": L10().filterAssembly, + "help_text": L10().filterAssemblyDetail + }, + "component": { + "label": L10().filterComponent, + "help_text": L10().filterComponentDetail, + }, + "is_template": { + "label": L10().filterTemplate, + "help_text": L10().filterTemplateDetail + }, + "trackable": { + "label": L10().filterTrackable, + "help_text": L10().filterTrackableDetail, + }, + "virtual": { + "label": L10().filterVirtual, + "help_text": L10().filterVirtualDetail, + }, + "has_stock": { + "label": L10().filterInStock, + "help_text": L10().filterInStockDetail, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - final page = await InvenTreePart().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = + await InvenTreePart().listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreePart part = model as InvenTreePart; return ListTile( title: Text(part.fullname), subtitle: Text(part.description), - trailing: Text( - part.stockString(), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold - ) - ), + trailing: Text(part.stockString(), + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), leading: InvenTreeAPI().getThumbnail(part.thumbnail), onTap: () { part.goToDetailPage(context); }, ); } -} \ No newline at end of file +} diff --git a/lib/widget/part/part_parameter_widget.dart b/lib/widget/part/part_parameter_widget.dart index 53e772da..63d6a461 100644 --- a/lib/widget/part/part_parameter_widget.dart +++ b/lib/widget/part/part_parameter_widget.dart @@ -11,7 +11,6 @@ import "package:inventree/widget/refreshable_state.dart"; * Widget for displaying a list of parameters associated with a given Part instance */ class PartParameterWidget extends StatefulWidget { - const PartParameterWidget(this.part); final InvenTreePart part; @@ -20,7 +19,6 @@ class PartParameterWidget extends StatefulWidget { _ParameterWidgetState createState() => _ParameterWidgetState(); } - class _ParameterWidgetState extends RefreshableState { _ParameterWidgetState(); @@ -36,28 +34,20 @@ class _ParameterWidgetState extends RefreshableState { @override Widget getBody(BuildContext context) { - - Map filters = { - "part": widget.part.pk.toString() - }; + Map filters = {"part": widget.part.pk.toString()}; return Column( - children: [ - Expanded( - child: PaginatedParameterList(filters) - ) - ], + children: [Expanded(child: PaginatedParameterList(filters))], ); } } - /* * Widget for displaying a paginated list of Part parameters */ class PaginatedParameterList extends PaginatedSearchWidget { - - const PaginatedParameterList(Map filters) : super(filters: filters); + const PaginatedParameterList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().parameters; @@ -66,51 +56,43 @@ class PaginatedParameterList extends PaginatedSearchWidget { _PaginatedParameterState createState() => _PaginatedParameterState(); } - -class _PaginatedParameterState extends PaginatedSearchState { - +class _PaginatedParameterState + extends PaginatedSearchState { _PaginatedParameterState() : super(); @override String get prefix => "parameters_"; @override - Map get orderingOptions => { - - }; + Map get orderingOptions => {}; @override Map> get filterOptions => { - // TODO - }; + // TODO + }; @override - Future requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreePartParameter().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreePartParameter() + .listPaginated(limit, offset, filters: params); return page; } Future editParameter(InvenTreePartParameter parameter) async { - // Checkbox values are handled separately if (parameter.is_checkbox) { return; } else { - parameter.editForm( - context, - L10().editParameter, - onSuccess: (data) async { - updateSearchTerm(); - } - ); + parameter.editForm(context, L10().editParameter, onSuccess: (data) async { + updateSearchTerm(); + }); } } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreePartParameter parameter = model as InvenTreePartParameter; String title = parameter.name; @@ -123,27 +105,27 @@ class _PaginatedParameterState extends PaginatedSearchState { - @override String getAppBarTitle() { return L10().partPricing; @@ -25,174 +25,108 @@ class _PartPricingWidgetState extends RefreshableState { @override List getTiles(BuildContext context) { - List tiles = [ Card( - child: ListTile( - title: Text(widget.part.fullname), - subtitle: Text(widget.part.description), - leading: api.getThumbnail(widget.part.thumbnail) - ) - ), + child: ListTile( + title: Text(widget.part.fullname), + subtitle: Text(widget.part.description), + leading: api.getThumbnail(widget.part.thumbnail))), ]; if (widget.partPricing == null) { - tiles.add( - ListTile( - title: Text(L10().noPricingAvailable), - subtitle: Text(L10().noPricingDataFound), - ) - ); + tiles.add(ListTile( + title: Text(L10().noPricingAvailable), + subtitle: Text(L10().noPricingDataFound), + )); return tiles; } final pricing = widget.partPricing!; - tiles.add( - ListTile( - title: Text(L10().currency), - trailing: Text(pricing.currency), - ) - ); + tiles.add(ListTile( + title: Text(L10().currency), + trailing: Text(pricing.currency), + )); - tiles.add( - ListTile( - title: Text(L10().priceRange), - trailing: Text( - formatPriceRange( - pricing.overallMin, - pricing.overallMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().priceRange), + trailing: Text(formatPriceRange(pricing.overallMin, pricing.overallMax, + currency: pricing.currency)), + )); if (pricing.overallMin != null) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().priceOverrideMin), - trailing: Text( - renderCurrency(pricing.overallMin, pricing.overrideMinCurrency) - ) - ) - ); + trailing: Text(renderCurrency( + pricing.overallMin, pricing.overrideMinCurrency)))); } if (pricing.overrideMax != null) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().priceOverrideMax), - trailing: Text( - renderCurrency(pricing.overallMax, pricing.overrideMaxCurrency) - ) - ) - ); + trailing: Text(renderCurrency( + pricing.overallMax, pricing.overrideMaxCurrency)))); } - tiles.add( - ListTile( - title: Text(L10().internalCost), - trailing: Text( - formatPriceRange( - pricing.internalCostMin, - pricing.internalCostMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().internalCost), + trailing: Text(formatPriceRange( + pricing.internalCostMin, pricing.internalCostMax, + currency: pricing.currency)), + )); if (widget.part.isTemplate) { - tiles.add( - ListTile( - title: Text(L10().variantCost), - trailing: Text( - formatPriceRange( - pricing.variantCostMin, - pricing.variantCostMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().variantCost), + trailing: Text(formatPriceRange( + pricing.variantCostMin, pricing.variantCostMax, + currency: pricing.currency)), + )); } if (widget.part.isAssembly) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().bomCost), - trailing: Text( - formatPriceRange( - pricing.bomCostMin, - pricing.bomCostMax, - currency: pricing.currency - ) - ) - ) - ); + trailing: Text(formatPriceRange( + pricing.bomCostMin, pricing.bomCostMax, + currency: pricing.currency)))); } if (widget.part.isPurchaseable) { - tiles.add( - ListTile( - title: Text(L10().purchasePrice), - trailing: Text( - formatPriceRange( - pricing.purchaseCostMin, - pricing.purchaseCostMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().purchasePrice), + trailing: Text(formatPriceRange( + pricing.purchaseCostMin, pricing.purchaseCostMax, + currency: pricing.currency)), + )); - tiles.add( - ListTile( - title: Text(L10().supplierPricing), - trailing: Text( - formatPriceRange( - pricing.supplierPriceMin, - pricing.supplierPriceMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().supplierPricing), + trailing: Text(formatPriceRange( + pricing.supplierPriceMin, pricing.supplierPriceMax, + currency: pricing.currency)), + )); } if (widget.part.isSalable) { tiles.add(Divider()); - tiles.add( - ListTile( - title: Text(L10().salePrice), - trailing: Text( - formatPriceRange( - pricing.salePriceMin, - pricing.salePriceMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().salePrice), + trailing: Text(formatPriceRange( + pricing.salePriceMin, pricing.salePriceMax, + currency: pricing.currency)), + )); - tiles.add( - ListTile( - title: Text(L10().saleHistory), - trailing: Text( - formatPriceRange( - pricing.saleHistoryMin, - pricing.saleHistoryMax, - currency: pricing.currency - ) - ), - ) - ); + tiles.add(ListTile( + title: Text(L10().saleHistory), + trailing: Text(formatPriceRange( + pricing.saleHistoryMin, pricing.saleHistoryMax, + currency: pricing.currency)), + )); } return tiles; } - } diff --git a/lib/widget/part/part_suppliers.dart b/lib/widget/part/part_suppliers.dart index 73b5cb32..dd74320f 100644 --- a/lib/widget/part/part_suppliers.dart +++ b/lib/widget/part/part_suppliers.dart @@ -10,19 +10,15 @@ import "package:inventree/inventree/company.dart"; import "package:inventree/widget/refreshable_state.dart"; class PartSupplierWidget extends StatefulWidget { - const PartSupplierWidget(this.part, {Key? key}) : super(key: key); final InvenTreePart part; @override _PartSupplierState createState() => _PartSupplierState(part); - } - class _PartSupplierState extends RefreshableState { - _PartSupplierState(this.part); final InvenTreePart part; @@ -46,7 +42,6 @@ class _PartSupplierState extends RefreshableState { } Widget _supplierPartTile(BuildContext context, int index) { - InvenTreeSupplierPart _part = _supplierParts[index]; return ListTile( @@ -73,5 +68,4 @@ class _PartSupplierState extends RefreshableState { itemBuilder: _supplierPartTile, ); } - -} \ No newline at end of file +} diff --git a/lib/widget/progress.dart b/lib/widget/progress.dart index 3deb9387..9c03b832 100644 --- a/lib/widget/progress.dart +++ b/lib/widget/progress.dart @@ -1,5 +1,3 @@ - - import "dart:io"; import "package:flutter/material.dart"; @@ -7,17 +5,11 @@ import "package:flutter_overlay_loader/flutter_overlay_loader.dart"; import "package:inventree/app_colors.dart"; import "package:one_context/one_context.dart"; - /* * A simplified linear progress bar widget, * with standardized color depiction */ -Widget ProgressBar( - double value, -{ - double maximum = 1.0 -}) { - +Widget ProgressBar(double value, {double maximum = 1.0}) { double v = 0; if (value <= 0 || maximum <= 0) { @@ -33,20 +25,14 @@ Widget ProgressBar( ); } - /* * Construct a circular progress indicator */ Widget progressIndicator() { - - return Center( - child: CircularProgressIndicator() - ); + return Center(child: CircularProgressIndicator()); } - void showLoadingOverlay() { - // Do not show overlay if running unit tests if (Platform.environment.containsKey("FLUTTER_TEST")) { return; @@ -58,13 +44,11 @@ void showLoadingOverlay() { return; } - Loader.show( - context, - themeData: Theme.of(context).copyWith(colorScheme: ColorScheme.fromSwatch()) - ); + Loader.show(context, + themeData: + Theme.of(context).copyWith(colorScheme: ColorScheme.fromSwatch())); } - void hideLoadingOverlay() { if (Loader.isShown) { Loader.hide(); diff --git a/lib/widget/refreshable_state.dart b/lib/widget/refreshable_state.dart index 59e70fab..78809ced 100644 --- a/lib/widget/refreshable_state.dart +++ b/lib/widget/refreshable_state.dart @@ -10,12 +10,10 @@ import "package:inventree/widget/back.dart"; import "package:inventree/widget/drawer.dart"; import "package:inventree/widget/search.dart"; - /* * Simple mixin class which defines simple methods for defining widget properties */ mixin BaseWidgetProperties { - /* * Return a list of appBar actions * By default, no appBar actions are available @@ -23,7 +21,9 @@ mixin BaseWidgetProperties { List appBarActions(BuildContext context) => []; // Return a title for the appBar (placeholder) - String getAppBarTitle() { return "--- app bar ---"; } + String getAppBarTitle() { + return "--- app bar ---"; + } // Function to construct a drawer (override if needed) Widget getDrawer(BuildContext context) { @@ -38,7 +38,6 @@ mixin BaseWidgetProperties { // Function to construct a body Widget getBody(BuildContext context) { - // Default implementation is to return a ListView // Override getTiles to replace the internal context return ListView( @@ -51,7 +50,6 @@ mixin BaseWidgetProperties { * Construct the top AppBar for this view */ AppBar? buildAppBar(BuildContext context, GlobalKey key) { - List tabs = getTabIcons(context); return AppBar( @@ -70,8 +68,8 @@ mixin BaseWidgetProperties { * - Button to access global search * - Button to access barcode scan */ - BottomAppBar? buildBottomAppBar(BuildContext context, GlobalKey key) { - + BottomAppBar? buildBottomAppBar( + BuildContext context, GlobalKey key) { const double iconSize = 40; List icons = [ @@ -89,12 +87,8 @@ mixin BaseWidgetProperties { iconSize: iconSize, onPressed: () { if (InvenTreeAPI().checkConnection()) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => SearchWidget(true) - ) - ); + Navigator.push(context, + MaterialPageRoute(builder: (context) => SearchWidget(true))); } }, ), @@ -125,9 +119,7 @@ mixin BaseWidgetProperties { mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: icons, - ) - ) - ); + ))); } /* @@ -146,7 +138,6 @@ mixin BaseWidgetProperties { * Build out action buttons for a given widget */ Widget? buildSpeedDial(BuildContext context) { - final actions = actionButtons(context); final barcodeActions = barcodeButtons(context); @@ -157,29 +148,25 @@ mixin BaseWidgetProperties { List children = []; if (barcodeActions.isNotEmpty) { - children.add( - SpeedDial( - icon: Icons.qr_code_scanner, - activeIcon: Icons.close, - children: barcodeActions, - spacing: 14, - childPadding: const EdgeInsets.all(5), - spaceBetweenChildren: 15, - ) - ); + children.add(SpeedDial( + icon: Icons.qr_code_scanner, + activeIcon: Icons.close, + children: barcodeActions, + spacing: 14, + childPadding: const EdgeInsets.all(5), + spaceBetweenChildren: 15, + )); } if (actions.isNotEmpty) { - children.add( - SpeedDial( - icon: Icons.more_horiz, - activeIcon: Icons.close, - children: actions, - spacing: 14, - childPadding: const EdgeInsets.all(5), - spaceBetweenChildren: 15, - ) - ); + children.add(SpeedDial( + icon: Icons.more_horiz, + activeIcon: Icons.close, + children: actions, + spacing: 14, + childPadding: const EdgeInsets.all(5), + spaceBetweenChildren: 15, + )); } return Wrap( @@ -191,18 +178,16 @@ mixin BaseWidgetProperties { // Return list of "tabs" for this widget List getTabIcons(BuildContext context) => []; - } - /* * Abstract base class which provides generic "refresh" functionality. * * - Drag down and release to 'refresh' the widget * - Define some method which runs to 'refresh' the widget state */ -abstract class RefreshableState extends State with BaseWidgetProperties { - +abstract class RefreshableState extends State + with BaseWidgetProperties { final scaffoldKey = GlobalKey(); final refreshKey = GlobalKey(); @@ -235,7 +220,6 @@ abstract class RefreshableState extends State with // Refresh the widget - handler for custom request() method Future refresh(BuildContext context) async { - // Escape if the widget is no longer loaded if (!mounted) { return; @@ -259,13 +243,14 @@ abstract class RefreshableState extends State with @override Widget build(BuildContext context) { - // Save the context for future use _context = context; List tabs = getTabIcons(context); - Widget body = tabs.isEmpty ? getBody(context) : TabBarView(children: getTabs(context)); + Widget body = tabs.isEmpty + ? getBody(context) + : TabBarView(children: getTabs(context)); Scaffold view = Scaffold( key: scaffoldKey, @@ -274,27 +259,25 @@ abstract class RefreshableState extends State with floatingActionButton: buildSpeedDial(context), floatingActionButtonLocation: FloatingActionButtonLocation.miniEndDocked, body: RefreshIndicator( - key: refreshKey, - notificationPredicate: (ScrollNotification notification) { - return true; - }, - - onRefresh: () async { - refresh(context); - }, - child: body - ), + key: refreshKey, + notificationPredicate: (ScrollNotification notification) { + return true; + }, + onRefresh: () async { + refresh(context); + }, + child: body), bottomNavigationBar: buildBottomAppBar(context, scaffoldKey), ); // Default implementation is *not* tabbed if (tabs.isNotEmpty) { return DefaultTabController( - length: tabs.length, - child: view, + length: tabs.length, + child: view, ); } else { return view; } } -} \ No newline at end of file +} diff --git a/lib/widget/search.dart b/lib/widget/search.dart index f9ab6e0c..95aab031 100644 --- a/lib/widget/search.dart +++ b/lib/widget/search.dart @@ -23,21 +23,17 @@ import "package:inventree/widget/order/sales_order_list.dart"; import "package:inventree/widget/company/company_list.dart"; import "package:inventree/widget/company/supplier_part_list.dart"; - // Widget for performing database-wide search class SearchWidget extends StatefulWidget { - const SearchWidget(this.hasAppbar); final bool hasAppbar; @override _SearchDisplayState createState() => _SearchDisplayState(hasAppbar); - } class _SearchDisplayState extends RefreshableState { - _SearchDisplayState(this.hasAppBar) : super(); final _formKey = GlobalKey(); @@ -80,7 +76,6 @@ class _SearchDisplayState extends RefreshableState { * Determine if the search is still running */ bool isSearching() { - if (searchController.text.isEmpty) { return false; } @@ -128,7 +123,6 @@ class _SearchDisplayState extends RefreshableState { // Callback when the text is being edited // Incorporates a debounce timer to restrict search frequency void onSearchTextChanged(String text, {bool immediate = false}) { - if (debounceTimer?.isActive ?? false) { debounceTimer!.cancel(); } @@ -151,8 +145,7 @@ class _SearchDisplayState extends RefreshableState { * } * } */ - int getSearchResultCount(Map results, String key) { - + int getSearchResultCount(Map results, String key) { dynamic result = results[key]; if (result == null || result is! Map) { @@ -170,43 +163,50 @@ class _SearchDisplayState extends RefreshableState { // Actually perform the search query Future _perform_search(Map body) async { - InvenTreeAPI().post( - "search/", - body: body, - expectedStatusCode: 200).then((APIResponse response) { + InvenTreeAPI() + .post("search/", body: body, expectedStatusCode: 200) + .then((APIResponse response) { + String searchTerm = (body["search"] ?? "").toString(); - String searchTerm = (body["search"] ?? "").toString(); + // Only update if the results correspond to the current search term + if (searchTerm == searchController.text && mounted) { + decrementPendingSearches(); - // Only update if the results correspond to the current search term - if (searchTerm == searchController.text && mounted) { + Map results = {}; - decrementPendingSearches(); + if (response.isValid() && response.data is Map) { + results = response.data as Map; - Map results = {}; + setState(() { + nPartResults = + getSearchResultCount(results, InvenTreePart.MODEL_TYPE); + nCategoryResults = + getSearchResultCount(results, InvenTreePartCategory.MODEL_TYPE); + nStockResults = + getSearchResultCount(results, InvenTreeStockItem.MODEL_TYPE); + nLocationResults = getSearchResultCount( + results, InvenTreeStockLocation.MODEL_TYPE); + nPurchaseOrderResults = getSearchResultCount( + results, InvenTreePurchaseOrder.MODEL_TYPE); + nSalesOrderResults = + getSearchResultCount(results, InvenTreeSalesOrder.MODEL_TYPE); + nSupplierPartResults = + getSearchResultCount(results, InvenTreeSupplierPart.MODEL_TYPE); + nManufacturerPartResults = getSearchResultCount( + results, InvenTreeManufacturerPart.MODEL_TYPE); + nCompanyResults = + getSearchResultCount(results, InvenTreeCompany.MODEL_TYPE); - if (response.isValid() && response.data is Map) { - results = response.data as Map; - - setState(() { - nPartResults = getSearchResultCount(results, InvenTreePart.MODEL_TYPE); - nCategoryResults = getSearchResultCount(results, InvenTreePartCategory.MODEL_TYPE); - nStockResults = getSearchResultCount(results, InvenTreeStockItem.MODEL_TYPE); - nLocationResults = getSearchResultCount(results, InvenTreeStockLocation.MODEL_TYPE); - nPurchaseOrderResults = getSearchResultCount(results, InvenTreePurchaseOrder.MODEL_TYPE); - nSalesOrderResults = getSearchResultCount(results, InvenTreeSalesOrder.MODEL_TYPE); - nSupplierPartResults = getSearchResultCount(results, InvenTreeSupplierPart.MODEL_TYPE); - nManufacturerPartResults = getSearchResultCount(results, InvenTreeManufacturerPart.MODEL_TYPE); - nCompanyResults = getSearchResultCount(results, InvenTreeCompany.MODEL_TYPE); - - // Special case for company search results - nCustomerResults = getSearchResultCount(results, "customer"); - nManufacturerResults = getSearchResultCount(results, "manufacturer"); - nSupplierResults = getSearchResultCount(results, "supplier"); - }); - } else { - resetSearchResults(); - } + // Special case for company search results + nCustomerResults = getSearchResultCount(results, "customer"); + nManufacturerResults = + getSearchResultCount(results, "manufacturer"); + nSupplierResults = getSearchResultCount(results, "supplier"); + }); + } else { + resetSearchResults(); } + } }); } @@ -237,11 +237,9 @@ class _SearchDisplayState extends RefreshableState { // Consolidated search allows us to perform *all* searches in a single query if (api.supportsConsolidatedSearch) { - Map body = { "limit": 1, "search": term, - InvenTreePart.MODEL_TYPE: {}, InvenTreePartCategory.MODEL_TYPE: {}, InvenTreeStockItem.MODEL_TYPE: {}, @@ -262,7 +260,6 @@ class _SearchDisplayState extends RefreshableState { } if (body.isNotEmpty) { - if (mounted) { setState(() { nPendingSearches = 1; @@ -272,7 +269,6 @@ class _SearchDisplayState extends RefreshableState { _perform_search(body), ); } - } } else { legacySearch(term); @@ -283,7 +279,6 @@ class _SearchDisplayState extends RefreshableState { * Perform "legacy" search (without consolidated search API endpoint */ Future legacySearch(String term) async { - // Search parts if (InvenTreePart().canView) { nPendingSearches++; @@ -302,7 +297,11 @@ class _SearchDisplayState extends RefreshableState { // Search part categories if (InvenTreePartCategory().canView) { nPendingSearches++; - InvenTreePartCategory().count(searchQuery: term,).then((int n) { + InvenTreePartCategory() + .count( + searchQuery: term, + ) + .then((int n) { if (term == searchController.text) { if (mounted) { decrementPendingSearches(); @@ -346,13 +345,9 @@ class _SearchDisplayState extends RefreshableState { // Search purchase orders if (InvenTreePurchaseOrder().canView) { - nPendingSearches++; + nPendingSearches++; InvenTreePurchaseOrder().count( - searchQuery: term, - filters: { - "outstanding": "true" - } - ).then((int n) { + searchQuery: term, filters: {"outstanding": "true"}).then((int n) { if (term == searchController.text) { if (mounted) { decrementPendingSearches(); @@ -367,40 +362,37 @@ class _SearchDisplayState extends RefreshableState { @override List getTiles(BuildContext context) { - List tiles = []; // Search input - tiles.add( - ListTile( - title: TextFormField( - decoration: InputDecoration( - hintText: L10().queryEmpty, - ), - key: _formKey, - readOnly: false, - autofocus: true, - autocorrect: false, - controller: searchController, - onChanged: (String text) { - onSearchTextChanged(text); - }, - onFieldSubmitted: (String text) { - }, + tiles.add(ListTile( + title: TextFormField( + decoration: InputDecoration( + hintText: L10().queryEmpty, ), - trailing: GestureDetector( - child: Icon( - searchController.text.isEmpty ? TablerIcons.search : TablerIcons.backspace, - color: searchController.text.isEmpty ? COLOR_ACTION : COLOR_DANGER, - ), - onTap: () { - searchController.clear(); - onSearchTextChanged("", immediate: true); - }, + key: _formKey, + readOnly: false, + autofocus: true, + autocorrect: false, + controller: searchController, + onChanged: (String text) { + onSearchTextChanged(text); + }, + onFieldSubmitted: (String text) {}, + ), + trailing: GestureDetector( + child: Icon( + searchController.text.isEmpty + ? TablerIcons.search + : TablerIcons.backspace, + color: searchController.text.isEmpty ? COLOR_ACTION : COLOR_DANGER, ), - ) - - ); + onTap: () { + searchController.clear(); + onSearchTextChanged("", immediate: true); + }, + ), + )); String query = searchController.text; @@ -408,8 +400,7 @@ class _SearchDisplayState extends RefreshableState { // Part Results if (nPartResults > 0) { - results.add( - ListTile( + results.add(ListTile( title: Text(L10().parts), leading: Icon(TablerIcons.box), trailing: Text("${nPartResults}"), @@ -417,275 +408,188 @@ class _SearchDisplayState extends RefreshableState { Navigator.push( context, MaterialPageRoute( - builder: (context) => PartList( - { - "original_search": query - } - ) - ) - ); - } - ) - ); + builder: (context) => + PartList({"original_search": query}))); + })); } // Part Category Results if (nCategoryResults > 0) { - results.add( - ListTile( - title: Text(L10().partCategories), - leading: Icon(TablerIcons.sitemap), - trailing: Text("${nCategoryResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().partCategories), + leading: Icon(TablerIcons.sitemap), + trailing: Text("${nCategoryResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => PartCategoryList( - { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => + PartCategoryList({"original_search": query}))); + }, + )); } // Stock Item Results if (nStockResults > 0) { - results.add( - ListTile( - title: Text(L10().stockItems), - leading: Icon(TablerIcons.package), - trailing: Text("${nStockResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().stockItems), + leading: Icon(TablerIcons.package), + trailing: Text("${nStockResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => StockItemList( - { - "original_search": query, - } - ) - ) - ); - }, - ) - ); + builder: (context) => StockItemList({ + "original_search": query, + }))); + }, + )); } // Stock location results if (nLocationResults > 0) { - results.add( - ListTile( - title: Text(L10().stockLocations), - leading: Icon(TablerIcons.location), - trailing: Text("${nLocationResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().stockLocations), + leading: Icon(TablerIcons.location), + trailing: Text("${nLocationResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => StockLocationList( - { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => + StockLocationList({"original_search": query}))); + }, + )); } // Purchase orders if (nPurchaseOrderResults > 0) { - results.add( - ListTile( - title: Text(L10().purchaseOrders), - leading: Icon(TablerIcons.shopping_cart), - trailing: Text("${nPurchaseOrderResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().purchaseOrders), + leading: Icon(TablerIcons.shopping_cart), + trailing: Text("${nPurchaseOrderResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => PurchaseOrderListWidget( - filters: { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => PurchaseOrderListWidget( + filters: {"original_search": query}))); + }, + )); } // Sales orders if (nSalesOrderResults > 0) { - results.add( - ListTile( - title: Text(L10().salesOrders), - leading: Icon(TablerIcons.shopping_cart), - trailing: Text("${nSalesOrderResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().salesOrders), + leading: Icon(TablerIcons.shopping_cart), + trailing: Text("${nSalesOrderResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => SalesOrderListWidget( - filters: { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => SalesOrderListWidget( + filters: {"original_search": query}))); + }, + )); } // Company results if (nCompanyResults > 0) { - results.add( - ListTile( - title: Text(L10().companies), - leading: Icon(TablerIcons.building), - trailing: Text("${nCompanyResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().companies), + leading: Icon(TablerIcons.building), + trailing: Text("${nCompanyResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => CompanyListWidget( - L10().companies, - { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => CompanyListWidget( + L10().companies, {"original_search": query}))); + }, + )); } // Customer results if (nCustomerResults > 0) { - results.add( - ListTile( - title: Text(L10().customers), - leading: Icon(TablerIcons.building_store), - trailing: Text("${nCustomerResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().customers), + leading: Icon(TablerIcons.building_store), + trailing: Text("${nCustomerResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => CompanyListWidget( - L10().customers, - { - "original_search": query, - "is_customer": "true" - } - ) - ) - ); - }, - ) - ); + builder: (context) => CompanyListWidget(L10().customers, + {"original_search": query, "is_customer": "true"}))); + }, + )); } // Manufacturer results if (nManufacturerResults > 0) { - results.add( - ListTile( - title: Text(L10().manufacturers), - leading: Icon(TablerIcons.building_factory_2), - trailing: Text("${nManufacturerResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().manufacturers), + leading: Icon(TablerIcons.building_factory_2), + trailing: Text("${nManufacturerResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => CompanyListWidget( - L10().manufacturers, - { - "original_search": query, - "is_manufacturer": "true" - } - ) - ) - ); - }, - ) - ); + builder: (context) => CompanyListWidget(L10().manufacturers, + {"original_search": query, "is_manufacturer": "true"}))); + }, + )); } // Supplier results if (nSupplierResults > 0) { - results.add( - ListTile( - title: Text(L10().suppliers), - leading: Icon(TablerIcons.building_store), - trailing: Text("${nSupplierResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().suppliers), + leading: Icon(TablerIcons.building_store), + trailing: Text("${nSupplierResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => CompanyListWidget( - L10().suppliers, - { - "original_search": query, - "is_supplier": "true" - } - ) - ) - ); - }, - ) - ); + builder: (context) => CompanyListWidget(L10().suppliers, + {"original_search": query, "is_supplier": "true"}))); + }, + )); } // Supplier part results if (nSupplierPartResults > 0) { - results.add( - ListTile( - title: Text(L10().supplierParts), - leading: Icon(TablerIcons.box), - trailing: Text("${nSupplierPartResults}"), - onTap: () { - Navigator.push( + results.add(ListTile( + title: Text(L10().supplierParts), + leading: Icon(TablerIcons.box), + trailing: Text("${nSupplierPartResults}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => SupplierPartList( - { - "original_search": query - } - ) - ) - ); - }, - ) - ); + builder: (context) => + SupplierPartList({"original_search": query}))); + }, + )); } if (isSearching()) { - tiles.add( - ListTile( - title: Text(L10().searching), - leading: Icon(TablerIcons.search), - trailing: CircularProgressIndicator(), - ) - ); + tiles.add(ListTile( + title: Text(L10().searching), + leading: Icon(TablerIcons.search), + trailing: CircularProgressIndicator(), + )); } if (!isSearching() && results.isEmpty && searchController.text.isNotEmpty) { - tiles.add( - ListTile( - title: Text( - L10().queryNoResults, - style: TextStyle(fontStyle: FontStyle.italic), - ), - leading: Icon(TablerIcons.zoom_cancel), - ) - ); + tiles.add(ListTile( + title: Text( + L10().queryNoResults, + style: TextStyle(fontStyle: FontStyle.italic), + ), + leading: Icon(TablerIcons.zoom_cancel), + )); } else { for (Widget result in results) { tiles.add(result); @@ -694,5 +598,4 @@ class _SearchDisplayState extends RefreshableState { return tiles; } - } diff --git a/lib/widget/snacks.dart b/lib/widget/snacks.dart index 398a4840..a7cc22a2 100644 --- a/lib/widget/snacks.dart +++ b/lib/widget/snacks.dart @@ -8,8 +8,8 @@ import "package:inventree/l10.dart"; /* * Display a configurable 'snackbar' at the bottom of the screen */ -void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) { - +void showSnackIcon(String text, + {IconData? icon, Function()? onAction, bool? success, String? actionText}) { debug("showSnackIcon: '${text}'"); // Escape quickly if we do not have context @@ -34,7 +34,6 @@ void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? suc if (icon == null && onAction == null) { icon = TablerIcons.circle_check; } - } else if (success != null && success == false) { backgroundColor = Colors.deepOrange; @@ -54,26 +53,24 @@ void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? suc childs.add(Icon(icon)); } - OneContext().showSnackBar(builder: (context) => SnackBar( - content: GestureDetector( - child: Row( - children: childs - ), - onTap: () { - ScaffoldMessenger.of(context!).hideCurrentSnackBar(); - }, - ), - backgroundColor: backgroundColor, - action: onAction == null ? null : SnackBarAction( - label: _action, - onPressed: () { - // Immediately dismiss the notification - ScaffoldMessenger.of(context!).hideCurrentSnackBar(); - onAction(); - } - ), - duration: Duration(seconds: onAction == null ? 5 : 10), - ) - ); - -} \ No newline at end of file + OneContext().showSnackBar( + builder: (context) => SnackBar( + content: GestureDetector( + child: Row(children: childs), + onTap: () { + ScaffoldMessenger.of(context!).hideCurrentSnackBar(); + }, + ), + backgroundColor: backgroundColor, + action: onAction == null + ? null + : SnackBarAction( + label: _action, + onPressed: () { + // Immediately dismiss the notification + ScaffoldMessenger.of(context!).hideCurrentSnackBar(); + onAction(); + }), + duration: Duration(seconds: onAction == null ? 5 : 10), + )); +} diff --git a/lib/widget/spinner.dart b/lib/widget/spinner.dart index 3ac68c50..35b29c21 100644 --- a/lib/widget/spinner.dart +++ b/lib/widget/spinner.dart @@ -2,7 +2,6 @@ import "package:flutter/material.dart"; import "package:inventree/app_colors.dart"; class Spinner extends StatefulWidget { - const Spinner({ this.color = COLOR_GRAY_LIGHT, Key? key, @@ -27,12 +26,8 @@ class _SpinnerState extends State with SingleTickerProviderStateMixin { _controller = AnimationController( vsync: this, duration: Duration(milliseconds: 2000), - ) - ..repeat(); - _child = Icon( - widget.icon, - color: widget.color - ); + )..repeat(); + _child = Icon(widget.icon, color: widget.color); super.initState(); } @@ -50,4 +45,4 @@ class _SpinnerState extends State with SingleTickerProviderStateMixin { child: _child, ); } -} \ No newline at end of file +} diff --git a/lib/widget/stock/location_display.dart b/lib/widget/stock/location_display.dart index b12f834c..4d47c584 100644 --- a/lib/widget/stock/location_display.dart +++ b/lib/widget/stock/location_display.dart @@ -18,12 +18,10 @@ import "package:inventree/widget/snacks.dart"; import "package:inventree/widget/stock/stock_list.dart"; import "package:inventree/labels.dart"; - /* * Widget for displaying detail view for a single StockLocation instance */ class LocationDisplayWidget extends StatefulWidget { - LocationDisplayWidget(this.location, {Key? key}) : super(key: key); final InvenTreeStockLocation? location; @@ -35,7 +33,6 @@ class LocationDisplayWidget extends StatefulWidget { } class _LocationDisplayState extends RefreshableState { - _LocationDisplayState(this.location); final InvenTreeStockLocation? location; @@ -53,31 +50,24 @@ class _LocationDisplayState extends RefreshableState { // Add "locate" button if (location != null && api.supportsMixin("locate")) { - actions.add( - IconButton( - icon: Icon(Icons.travel_explore), - tooltip: L10().locateLocation, - onPressed: () async { - api.locateItemOrLocation(context, location: location!.pk); - } - ) - ); + actions.add(IconButton( + icon: Icon(Icons.travel_explore), + tooltip: L10().locateLocation, + onPressed: () async { + api.locateItemOrLocation(context, location: location!.pk); + })); } // Add "edit" button if (location != null && InvenTreeStockLocation().canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().editLocation, - onPressed: () { - _editLocationDialog(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().editLocation, + onPressed: () { + _editLocationDialog(context); + })); } - return actions; } @@ -88,63 +78,50 @@ class _LocationDisplayState extends RefreshableState { if (location != null) { // Scan items into this location if (InvenTreeStockItem().canEdit) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.qrcode), - label: L10().barcodeScanItem, - onTap: () { - scanBarcode( - context, - handler: StockLocationScanInItemsHandler(location!), - ).then((value) { - refresh(context); - }); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.qrcode), + label: L10().barcodeScanItem, + onTap: () { + scanBarcode( + context, + handler: StockLocationScanInItemsHandler(location!), + ).then((value) { + refresh(context); + }); + })); } if (api.supportsBarcodePOReceiveEndpoint) { - actions.add( - SpeedDialChild( - child: Icon(Icons.barcode_reader), - label: L10().scanReceivedParts, - onTap:() async { - scanBarcode( - context, - handler: POReceiveBarcodeHandler(location: location), - ); - }, - ) - ); + actions.add(SpeedDialChild( + child: Icon(Icons.barcode_reader), + label: L10().scanReceivedParts, + onTap: () async { + scanBarcode( + context, + handler: POReceiveBarcodeHandler(location: location), + ); + }, + )); } // Scan this location into another one if (InvenTreeStockLocation().canEdit) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.qrcode), - label: L10().transferStockLocation, - onTap: () { - scanBarcode( - context, - handler: ScanParentLocationHandler(location!), - ).then((value) { - refresh(context); - }); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.qrcode), + label: L10().transferStockLocation, + onTap: () { + scanBarcode( + context, + handler: ScanParentLocationHandler(location!), + ).then((value) { + refresh(context); + }); + })); } // Assign or un-assign barcodes - actions.add( - customBarcodeAction( - context, this, - location!.customBarcode, "stocklocation", - location!.pk - ) - ); + actions.add(customBarcodeAction(context, this, location!.customBarcode, + "stocklocation", location!.pk)); } return actions; @@ -156,46 +133,32 @@ class _LocationDisplayState extends RefreshableState { // Create new location if (InvenTreeStockLocation().canCreate) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.sitemap), - label: L10().locationCreate, - onTap: () async { - _newLocation(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.sitemap), + label: L10().locationCreate, + onTap: () async { + _newLocation(context); + })); } // Create new item if (InvenTreeStockItem().canCreate) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.packages), - label: L10().stockItemCreate, - onTap: () async { - _newStockItem(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.packages), + label: L10().stockItemCreate, + onTap: () async { + _newStockItem(context); + })); } if (widget.location != null && labels.isNotEmpty) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.printer), - label: L10().printLabel, - onTap: () async { - selectAndPrintLabel( - context, - labels, - widget.location!.pk, - "location", - "location=${widget.location!.pk}" - ); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.printer), + label: L10().printLabel, + onTap: () async { + selectAndPrintLabel(context, labels, widget.location!.pk, + "location", "location=${widget.location!.pk}"); + })); } return actions; @@ -211,14 +174,10 @@ class _LocationDisplayState extends RefreshableState { return; } - _loc.editForm( - context, - L10().editLocation, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().locationUpdated, success: true); - } - ); + _loc.editForm(context, L10().editLocation, onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().locationUpdated, success: true); + }); } @override @@ -238,22 +197,20 @@ class _LocationDisplayState extends RefreshableState { } List> _labels = []; - bool allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true); + bool allowLabelPrinting = await InvenTreeSettingsManager() + .getBool(INV_ENABLE_LABEL_PRINTING, true); allowLabelPrinting &= api.supportsMixin("labels"); if (allowLabelPrinting) { - if (widget.location != null) { - - String model_type = api.supportsModernLabelPrinting ? InvenTreeStockLocation.MODEL_TYPE : "location"; - String item_key = api.supportsModernLabelPrinting ? "items" : "location"; + String model_type = api.supportsModernLabelPrinting + ? InvenTreeStockLocation.MODEL_TYPE + : "location"; + String item_key = + api.supportsModernLabelPrinting ? "items" : "location"; _labels = await getLabelTemplates( - model_type, - { - item_key: widget.location!.pk.toString() - } - ); + model_type, {item_key: widget.location!.pk.toString()}); } } @@ -267,28 +224,22 @@ class _LocationDisplayState extends RefreshableState { Future _newLocation(BuildContext context) async { int pk = location?.pk ?? -1; - InvenTreeStockLocation().createForm( - context, - L10().locationCreate, - data: { - "parent": (pk > 0) ? pk : null, - }, - onSuccess: (result) async { - Map data = result as Map; + InvenTreeStockLocation().createForm(context, L10().locationCreate, data: { + "parent": (pk > 0) ? pk : null, + }, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var loc = InvenTreeStockLocation.fromJson(data); - loc.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var loc = InvenTreeStockLocation.fromJson(data); + loc.goToDetailPage(context); + } + }); } /* * Launch a dialog form to create a new stock item */ Future _newStockItem(BuildContext context) async { - var fields = InvenTreeStockItem().formFields(); // Serial number field is not required here @@ -300,73 +251,65 @@ class _LocationDisplayState extends RefreshableState { data["location"] = location!.pk; } - InvenTreeStockItem().createForm( - context, - L10().stockItemCreate, - data: data, - fields: fields, - onSuccess: (result) async { - Map data = result as Map; + InvenTreeStockItem().createForm(context, L10().stockItemCreate, + data: data, fields: fields, onSuccess: (result) async { + Map data = result as Map; - if (data.containsKey("pk")) { - var item = InvenTreeStockItem.fromJson(data); - item.goToDetailPage(context); - } - } - ); + if (data.containsKey("pk")) { + var item = InvenTreeStockItem.fromJson(data); + item.goToDetailPage(context); + } + }); } Widget locationDescriptionCard({bool includeActions = true}) { if (location == null) { return Card( child: ListTile( - title: Text( - L10().stockTopLevel, - style: TextStyle(fontStyle: FontStyle.italic) - ), - leading: Icon(TablerIcons.packages), - ) - ); + title: Text(L10().stockTopLevel, + style: TextStyle(fontStyle: FontStyle.italic)), + leading: Icon(TablerIcons.packages), + )); } else { List children = [ ListTile( - title: Text("${location!.name}"), - subtitle: Text("${location!.description}"), - leading: location!.customIcon == null ? Icon(TablerIcons.packages) : Icon(location!.customIcon) - ), + title: Text("${location!.name}"), + subtitle: Text("${location!.description}"), + leading: location!.customIcon == null + ? Icon(TablerIcons.packages) + : Icon(location!.customIcon)), ]; if (includeActions) { - children.add( - ListTile( - title: Text(L10().parentLocation), - subtitle: Text("${location!.parentPathString}"), - leading: Icon(TablerIcons.arrow_move_up, color: COLOR_ACTION), - onTap: () async { - int parentId = location?.parentId ?? -1; + children.add(ListTile( + title: Text(L10().parentLocation), + subtitle: Text("${location!.parentPathString}"), + leading: Icon(TablerIcons.arrow_move_up, color: COLOR_ACTION), + onTap: () async { + int parentId = location?.parentId ?? -1; - if (parentId < 0) { - Navigator.push(context, MaterialPageRoute( + if (parentId < 0) { + Navigator.push( + context, + MaterialPageRoute( builder: (context) => LocationDisplayWidget(null))); - } else { - showLoadingOverlay(); - var loc = await InvenTreeStockLocation().get(parentId); - hideLoadingOverlay(); + } else { + showLoadingOverlay(); + var loc = await InvenTreeStockLocation().get(parentId); + hideLoadingOverlay(); - if (loc is InvenTreeStockLocation) { - loc.goToDetailPage(context); - } - } - }, - ) - ); + if (loc is InvenTreeStockLocation) { + loc.goToDetailPage(context); + } + } + }, + )); } return Card( child: Column( - children: children, - ) - ); + children: children, + )); } } @@ -388,7 +331,6 @@ class _LocationDisplayState extends RefreshableState { // Construct the "details" panel List detailTiles() { - Map filters = {}; int? parent = location?.pk; diff --git a/lib/widget/stock/location_list.dart b/lib/widget/stock/location_list.dart index 05cb204a..6c2c850c 100644 --- a/lib/widget/stock/location_list.dart +++ b/lib/widget/stock/location_list.dart @@ -7,9 +7,7 @@ import "package:inventree/widget/paginator.dart"; import "package:inventree/widget/refreshable_state.dart"; import "package:inventree/l10.dart"; - class StockLocationList extends StatefulWidget { - const StockLocationList(this.filters); final Map filters; @@ -18,9 +16,7 @@ class StockLocationList extends StatefulWidget { _StockLocationListState createState() => _StockLocationListState(filters); } - class _StockLocationListState extends RefreshableState { - _StockLocationListState(this.filters); final Map filters; @@ -34,50 +30,50 @@ class _StockLocationListState extends RefreshableState { } } - class PaginatedStockLocationList extends PaginatedSearchWidget { - - const PaginatedStockLocationList(Map filters, {String title = ""}) : super(filters: filters, title: title); + const PaginatedStockLocationList(Map filters, + {String title = ""}) + : super(filters: filters, title: title); @override String get searchTitle => title.isNotEmpty ? title : L10().stockLocations; @override - _PaginatedStockLocationListState createState() => _PaginatedStockLocationListState(); + _PaginatedStockLocationListState createState() => + _PaginatedStockLocationListState(); } - -class _PaginatedStockLocationListState extends PaginatedSearchState { - +class _PaginatedStockLocationListState + extends PaginatedSearchState { _PaginatedStockLocationListState() : super(); @override Map get orderingOptions => { - "name": L10().name, - "items": L10().stockItems, - "level": L10().level, - }; + "name": L10().name, + "items": L10().stockItems, + "level": L10().level, + }; @override Map> get filterOptions => { - "cascade": { - "label": L10().includeSublocations, - "help_text": L10().includeSublocationsDetail, - "tristate": false, - } - }; + "cascade": { + "label": L10().includeSublocations, + "help_text": L10().includeSublocationsDetail, + "tristate": false, + } + }; @override - Future requestPage(int limit, int offset, Map params) async { - - final page = await InvenTreeStockLocation().listPaginated(limit, offset, filters: params); + Future requestPage( + int limit, int offset, Map params) async { + final page = await InvenTreeStockLocation() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeStockLocation location = model as InvenTreeStockLocation; return ListTile( @@ -90,4 +86,4 @@ class _PaginatedStockLocationListState extends PaginatedSearchState _StockItemDisplayState(); } - class _StockItemDisplayState extends RefreshableState { - _StockItemDisplayState(); @override @@ -61,27 +57,21 @@ class _StockItemDisplayState extends RefreshableState { List actions = []; if (api.supportsMixin("locate")) { - actions.add( - IconButton( - icon: Icon(Icons.travel_explore), - tooltip: L10().locateItem, - onPressed: () async { - api.locateItemOrLocation(context, item: widget.item.pk); - } - ) - ); + actions.add(IconButton( + icon: Icon(Icons.travel_explore), + tooltip: L10().locateItem, + onPressed: () async { + api.locateItemOrLocation(context, item: widget.item.pk); + })); } if (widget.item.canEdit) { - actions.add( - IconButton( - icon: Icon(TablerIcons.edit), - tooltip: L10().editItem, - onPressed: () { - _editStockItem(context); - } - ) - ); + actions.add(IconButton( + icon: Icon(TablerIcons.edit), + tooltip: L10().editItem, + onPressed: () { + _editStockItem(context); + })); } return actions; @@ -89,79 +79,56 @@ class _StockItemDisplayState extends RefreshableState { @override List actionButtons(BuildContext context) { - List actions = []; if (widget.item.canEdit) { - // Stock adjustment actions available if item is *not* serialized if (!widget.item.isSerialized()) { + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_check, color: Colors.blue), + label: L10().countStock, + onTap: _countStockDialog, + )); - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_check, color: Colors.blue), - label: L10().countStock, - onTap: _countStockDialog, - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_minus, color: Colors.red), + label: L10().removeStock, + onTap: _removeStockDialog, + )); - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_minus, color: Colors.red), - label: L10().removeStock, - onTap: _removeStockDialog, - ) - ); - - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.circle_plus, color: Colors.green), - label: L10().addStock, - onTap: _addStockDialog, - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.circle_plus, color: Colors.green), + label: L10().addStock, + onTap: _addStockDialog, + )); } // Transfer item - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.transfer), label: L10().transferStock, onTap: () { _transferStockDialog(context); - } - ) - ); + })); } if (labels.isNotEmpty) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.printer), label: L10().printLabel, onTap: () async { - selectAndPrintLabel( - context, - labels, - widget.item.pk, - "stock", - "item=${widget.item.pk}" - ); - } - ) - ); + selectAndPrintLabel(context, labels, widget.item.pk, "stock", + "item=${widget.item.pk}"); + })); } if (widget.item.canDelete) { - actions.add( - SpeedDialChild( - child: Icon(TablerIcons.trash, color: Colors.red), - label: L10().stockItemDelete, - onTap: () { - _deleteItem(context); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(TablerIcons.trash, color: Colors.red), + label: L10().stockItemDelete, + onTap: () { + _deleteItem(context); + })); } return actions; @@ -173,28 +140,19 @@ class _StockItemDisplayState extends RefreshableState { if (widget.item.canEdit) { // Scan item into location - actions.add( - SpeedDialChild( - child: Icon(Icons.qr_code_scanner), - label: L10().scanIntoLocation, - onTap: () { - scanBarcode( - context, - handler: StockItemScanIntoLocationHandler(widget.item) - ).then((ctx) { - refresh(context); - }); - } - ) - ); + actions.add(SpeedDialChild( + child: Icon(Icons.qr_code_scanner), + label: L10().scanIntoLocation, + onTap: () { + scanBarcode(context, + handler: StockItemScanIntoLocationHandler(widget.item)) + .then((ctx) { + refresh(context); + }); + })); - actions.add( - customBarcodeAction( - context, this, - widget.item.customBarcode, - "stockitem", widget.item.pk - ) - ); + actions.add(customBarcodeAction(context, this, widget.item.customBarcode, + "stockitem", widget.item.pk)); } return actions; @@ -217,8 +175,10 @@ class _StockItemDisplayState extends RefreshableState { @override Future request(BuildContext context) async { await api.StockStatus.load(); - stockShowHistory = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_HISTORY, false) as bool; - stockShowTests = await InvenTreeSettingsManager().getValue(INV_STOCK_SHOW_TESTS, true) as bool; + stockShowHistory = await InvenTreeSettingsManager() + .getValue(INV_STOCK_SHOW_HISTORY, false) as bool; + stockShowTests = await InvenTreeSettingsManager() + .getValue(INV_STOCK_SHOW_TESTS, true) as bool; final bool result = widget.item.pk > 0 && await widget.item.reload(); @@ -238,7 +198,6 @@ class _StockItemDisplayState extends RefreshableState { // Request test results (async) if (stockShowTests) { widget.item.getTestResults().then((value) { - if (mounted) { setState(() { // Update @@ -248,7 +207,9 @@ class _StockItemDisplayState extends RefreshableState { } // Request the number of attachments - InvenTreeStockItemAttachment().countAttachments(widget.item.pk).then((int value) { + InvenTreeStockItemAttachment() + .countAttachments(widget.item.pk) + .then((int value) { if (mounted) { setState(() { attachmentCount = value; @@ -259,12 +220,13 @@ class _StockItemDisplayState extends RefreshableState { // Request SalesOrder information if (widget.item.hasSalesOrder) { InvenTreeSalesOrder().get(widget.item.salesOrderId).then((instance) => { - if (mounted) { - setState(() { - salesOrder = instance as InvenTreeSalesOrder?; - }) - } - }); + if (mounted) + { + setState(() { + salesOrder = instance as InvenTreeSalesOrder?; + }) + } + }); } else { if (mounted) { setState(() { @@ -276,12 +238,13 @@ class _StockItemDisplayState extends RefreshableState { // Request Customer information if (widget.item.hasCustomer) { InvenTreeCompany().get(widget.item.customerId).then((instance) => { - if (mounted) { - setState(() { - customer = instance as InvenTreeCompany?; - }) - } - }); + if (mounted) + { + setState(() { + customer = instance as InvenTreeCompany?; + }) + } + }); } else { if (mounted) { setState(() { @@ -291,22 +254,20 @@ class _StockItemDisplayState extends RefreshableState { } List> _labels = []; - bool allowLabelPrinting = await InvenTreeSettingsManager().getBool(INV_ENABLE_LABEL_PRINTING, true); + bool allowLabelPrinting = await InvenTreeSettingsManager() + .getBool(INV_ENABLE_LABEL_PRINTING, true); allowLabelPrinting &= api.supportsMixin("labels"); // Request information on labels available for this stock item if (allowLabelPrinting) { - - String model_type = api.supportsModernLabelPrinting ? InvenTreeStockItem.MODEL_TYPE : "stock"; + String model_type = api.supportsModernLabelPrinting + ? InvenTreeStockItem.MODEL_TYPE + : "stock"; String item_key = api.supportsModernLabelPrinting ? "items" : "item"; // Clear the existing labels list _labels = await getLabelTemplates( - model_type, - { - item_key: widget.item.pk.toString() - } - ); + model_type, {item_key: widget.item.pk.toString()}); } if (mounted) { @@ -318,7 +279,6 @@ class _StockItemDisplayState extends RefreshableState { /// Delete the stock item from the database Future _deleteItem(BuildContext context) async { - confirmationDialog( L10().stockItemDelete, L10().stockItemDeleteConfirm, @@ -327,7 +287,7 @@ class _StockItemDisplayState extends RefreshableState { acceptText: L10().delete, onAccept: () async { final bool result = await widget.item.delete(); - + if (result) { Navigator.of(context).pop(); showSnackIcon(L10().stockItemDeleteSuccess, success: true); @@ -336,11 +296,9 @@ class _StockItemDisplayState extends RefreshableState { } }, ); - } - Future _editStockItem(BuildContext context) async { - + Future _editStockItem(BuildContext context) async { var fields = InvenTreeStockItem().formFields(); // Some fields we don't want to edit! @@ -353,23 +311,17 @@ class _StockItemDisplayState extends RefreshableState { fields.remove("serial"); } - widget.item.editForm( - context, - L10().editItem, - fields: fields, - onSuccess: (data) async { - refresh(context); - showSnackIcon(L10().stockItemUpdated, success: true); - } - ); - + widget.item.editForm(context, L10().editItem, fields: fields, + onSuccess: (data) async { + refresh(context); + showSnackIcon(L10().stockItemUpdated, success: true); + }); } /* * Launch a dialog to 'add' quantity to this StockItem */ - Future _addStockDialog() async { - + Future _addStockDialog() async { Map fields = { "pk": { "parent": "items", @@ -386,21 +338,14 @@ class _StockItemDisplayState extends RefreshableState { }; launchApiForm( - context, - L10().addStock, - InvenTreeStockItem.addStockUrl(), - fields, - method: "POST", - icon: TablerIcons.circle_plus, - onSuccess: (data) async { - _stockUpdateMessage(true); - refresh(context); - } - ); + context, L10().addStock, InvenTreeStockItem.addStockUrl(), fields, + method: "POST", icon: TablerIcons.circle_plus, onSuccess: (data) async { + _stockUpdateMessage(true); + refresh(context); + }); } void _stockUpdateMessage(bool result) { - if (result) { showSnackIcon(L10().stockItemUpdated, success: true); } @@ -410,7 +355,6 @@ class _StockItemDisplayState extends RefreshableState { * Launch a dialog to 'remove' quantity from this StockItem */ void _removeStockDialog() { - Map fields = { "pk": { "parent": "items", @@ -427,21 +371,15 @@ class _StockItemDisplayState extends RefreshableState { }; launchApiForm( - context, - L10().removeStock, - InvenTreeStockItem.removeStockUrl(), - fields, + context, L10().removeStock, InvenTreeStockItem.removeStockUrl(), fields, method: "POST", - icon: TablerIcons.circle_minus, - onSuccess: (data) async { - _stockUpdateMessage(true); - refresh(context); - } - ); + icon: TablerIcons.circle_minus, onSuccess: (data) async { + _stockUpdateMessage(true); + refresh(context); + }); } - Future _countStockDialog() async { - + Future _countStockDialog() async { Map fields = { "pk": { "parent": "items", @@ -458,82 +396,60 @@ class _StockItemDisplayState extends RefreshableState { }; launchApiForm( - context, - L10().countStock, - InvenTreeStockItem.countStockUrl(), - fields, + context, L10().countStock, InvenTreeStockItem.countStockUrl(), fields, method: "POST", - icon: TablerIcons.clipboard_check, - onSuccess: (data) async { - _stockUpdateMessage(true); - refresh(context); - } - ); + icon: TablerIcons.clipboard_check, onSuccess: (data) async { + _stockUpdateMessage(true); + refresh(context); + }); } /* * Launches an API Form to transfer this stock item to a new location */ - Future _transferStockDialog(BuildContext context) async { - + Future _transferStockDialog(BuildContext context) async { Map fields = widget.item.transferFields(); - launchApiForm( - context, - L10().transferStock, - InvenTreeStockItem.transferStockUrl(), - fields, - method: "POST", - icon: TablerIcons.transfer, - onSuccess: (data) async { - _stockUpdateMessage(true); - refresh(context); - } - ); + launchApiForm(context, L10().transferStock, + InvenTreeStockItem.transferStockUrl(), fields, + method: "POST", icon: TablerIcons.transfer, onSuccess: (data) async { + _stockUpdateMessage(true); + refresh(context); + }); } Widget headerTile() { - Widget? trailing; if (!widget.item.isInStock) { - trailing = Text( - L10().unavailable, - style: TextStyle( - color: COLOR_DANGER - ) - ); + trailing = Text(L10().unavailable, style: TextStyle(color: COLOR_DANGER)); } else if (!widget.item.isSerialized()) { - trailing = Text( - widget.item.quantityString(), + trailing = Text(widget.item.quantityString(), style: TextStyle( fontSize: 20, color: api.StockStatus.color(widget.item.status), - ) - ); + )); } return Card( - child: ListTile( - title: Text(widget.item.partName), - subtitle: Text(widget.item.partDescription), - leading: InvenTreeAPI().getThumbnail(widget.item.partImage), - trailing: trailing, - onTap: () async { - if (widget.item.partId > 0) { + child: ListTile( + title: Text(widget.item.partName), + subtitle: Text(widget.item.partDescription), + leading: InvenTreeAPI().getThumbnail(widget.item.partImage), + trailing: trailing, + onTap: () async { + if (widget.item.partId > 0) { + showLoadingOverlay(); + var part = await InvenTreePart().get(widget.item.partId); + hideLoadingOverlay(); - showLoadingOverlay(); - var part = await InvenTreePart().get(widget.item.partId); - hideLoadingOverlay(); - - if (part is InvenTreePart) { - part.goToDetailPage(context); - } + if (part is InvenTreePart) { + part.goToDetailPage(context); } - }, - //trailing: Text(item.serialOrQuantityDisplay()), - ) - ); + } + }, + //trailing: Text(item.serialOrQuantityDisplay()), + )); } /* @@ -564,9 +480,9 @@ class _StockItemDisplayState extends RefreshableState { ), onTap: () async { if (widget.item.locationId > 0) { - showLoadingOverlay(); - var loc = await InvenTreeStockLocation().get(widget.item.locationId); + var loc = + await InvenTreeStockLocation().get(widget.item.locationId); hideLoadingOverlay(); if (loc is InvenTreeStockLocation) { @@ -577,37 +493,32 @@ class _StockItemDisplayState extends RefreshableState { ), ); } else { - tiles.add( - ListTile( - title: Text(L10().stockLocation), - leading: Icon(TablerIcons.location), - subtitle: Text(L10().locationNotSet), - ) - ); + tiles.add(ListTile( + title: Text(L10().stockLocation), + leading: Icon(TablerIcons.location), + subtitle: Text(L10().locationNotSet), + )); } // Quantity information if (widget.item.isSerialized()) { - tiles.add( - ListTile( - title: Text(L10().serialNumber), - leading: Icon(TablerIcons.hash), - subtitle: Text("${widget.item.serialNumber}"), - ) - ); + tiles.add(ListTile( + title: Text(L10().serialNumber), + leading: Icon(TablerIcons.hash), + subtitle: Text("${widget.item.serialNumber}"), + )); } else if (widget.item.isInStock) { - tiles.add( - ListTile( - title: widget.item.allocated > 0 ? Text(L10().quantityAvailable) : Text(L10().quantity), - leading: Icon(TablerIcons.packages), - trailing: Text("${widget.item.quantityString()}"), - ) - ); + tiles.add(ListTile( + title: widget.item.allocated > 0 + ? Text(L10().quantityAvailable) + : Text(L10().quantity), + leading: Icon(TablerIcons.packages), + trailing: Text("${widget.item.quantityString()}"), + )); } if (!widget.item.isInStock) { - tiles.add( - ListTile( + tiles.add(ListTile( leading: Icon(TablerIcons.box_off), title: Text( L10().unavailable, @@ -616,258 +527,207 @@ class _StockItemDisplayState extends RefreshableState { fontWeight: FontWeight.bold, ), ), - subtitle: Text( - L10().unavailableDetail, - style: TextStyle( - color: COLOR_DANGER - ) - ) - ) - ); + subtitle: Text(L10().unavailableDetail, + style: TextStyle(color: COLOR_DANGER)))); } // Stock item status information - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().status), leading: Icon(TablerIcons.help_circle), - trailing: Text( - api.StockStatus.label(widget.item.status), - style: TextStyle( - color: api.StockStatus.color(widget.item.status), - ) - ) - ) - ); + trailing: Text(api.StockStatus.label(widget.item.status), + style: TextStyle( + color: api.StockStatus.color(widget.item.status), + )))); // Supplier part information (if available) if (widget.item.supplierPartId > 0) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().supplierPart), subtitle: Text(widget.item.supplierSKU), leading: Icon(TablerIcons.building, color: COLOR_ACTION), - trailing: InvenTreeAPI().getThumbnail(widget.item.supplierImage, hideIfNull: true), + trailing: InvenTreeAPI() + .getThumbnail(widget.item.supplierImage, hideIfNull: true), onTap: () async { showLoadingOverlay(); - var sp = await InvenTreeSupplierPart().get( - widget.item.supplierPartId); + var sp = + await InvenTreeSupplierPart().get(widget.item.supplierPartId); hideLoadingOverlay(); if (sp is InvenTreeSupplierPart) { Navigator.push( - context, MaterialPageRoute( - builder: (context) => SupplierPartDetailWidget(sp)) - ); + context, + MaterialPageRoute( + builder: (context) => SupplierPartDetailWidget(sp))); } - } - ) - ); + })); } if (widget.item.isBuilding) { - tiles.add( - ListTile( - title: Text(L10().inProduction), - leading: Icon(TablerIcons.tools), - subtitle: Text(L10().inProductionDetail), - onTap: () { - // TODO: Click through to the "build order" - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().inProduction), + leading: Icon(TablerIcons.tools), + subtitle: Text(L10().inProductionDetail), + onTap: () { + // TODO: Click through to the "build order" + }, + )); } if (widget.item.hasSalesOrder && salesOrder != null) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().salesOrder), subtitle: Text(salesOrder?.description ?? ""), leading: Icon(TablerIcons.truck_delivery, color: COLOR_ACTION), trailing: Text(salesOrder?.reference ?? ""), onTap: () { salesOrder?.goToDetailPage(context); - } - ) - ); + })); } if (widget.item.hasCustomer && customer != null) { - tiles.add( - ListTile( - title: Text(L10().customer), - subtitle: Text(customer?.description ?? ""), - leading: Icon(TablerIcons.building_store, color: COLOR_ACTION), - trailing: Text(customer?.name ?? ""), - onTap: () { - customer?.goToDetailPage(context); - }, - ) - ); + tiles.add(ListTile( + title: Text(L10().customer), + subtitle: Text(customer?.description ?? ""), + leading: Icon(TablerIcons.building_store, color: COLOR_ACTION), + trailing: Text(customer?.name ?? ""), + onTap: () { + customer?.goToDetailPage(context); + }, + )); } if (widget.item.batch.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().batchCode), - subtitle: Text(widget.item.batch), - leading: Icon(TablerIcons.clipboard_text), - ) - ); + tiles.add(ListTile( + title: Text(L10().batchCode), + subtitle: Text(widget.item.batch), + leading: Icon(TablerIcons.clipboard_text), + )); } if (widget.item.packaging.isNotEmpty) { - tiles.add( - ListTile( - title: Text(L10().packaging), - subtitle: Text(widget.item.packaging), - leading: Icon(TablerIcons.package), - ) - ); + tiles.add(ListTile( + title: Text(L10().packaging), + subtitle: Text(widget.item.packaging), + leading: Icon(TablerIcons.package), + )); } if (expiryEnabled && widget.item.expiryDate != null) { - Widget? _expiryIcon; if (widget.item.expired) { - _expiryIcon = Text(L10().expiryExpired, style: TextStyle(color: COLOR_DANGER)); + _expiryIcon = + Text(L10().expiryExpired, style: TextStyle(color: COLOR_DANGER)); } else if (widget.item.stale) { - _expiryIcon = Text(L10().expiryStale, style: TextStyle(color: COLOR_WARNING)); + _expiryIcon = + Text(L10().expiryStale, style: TextStyle(color: COLOR_WARNING)); } - tiles.add( - ListTile( - title: Text(L10().expiryDate), - subtitle: Text(widget.item.expiryDateString), - leading: Icon(TablerIcons.calendar_x), - trailing: _expiryIcon, - ) - ); + tiles.add(ListTile( + title: Text(L10().expiryDate), + subtitle: Text(widget.item.expiryDateString), + leading: Icon(TablerIcons.calendar_x), + trailing: _expiryIcon, + )); } // Last update? if (widget.item.updatedDateString.isNotEmpty) { - - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().lastUpdated), subtitle: Text(widget.item.updatedDateString), - leading: Icon(TablerIcons.calendar) - ) - ); + leading: Icon(TablerIcons.calendar))); } // Stocktake? if (widget.item.stocktakeDateString.isNotEmpty) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().lastStocktake), subtitle: Text(widget.item.stocktakeDateString), - leading: Icon(TablerIcons.calendar) - ) - ); + leading: Icon(TablerIcons.calendar))); } if (widget.item.link.isNotEmpty) { - tiles.add( - ListTile( - title: Text("${widget.item.link}"), - leading: Icon(TablerIcons.link, color: COLOR_ACTION), - onTap: () { - widget.item.openLink(); - }, - ) - ); + tiles.add(ListTile( + title: Text("${widget.item.link}"), + leading: Icon(TablerIcons.link, color: COLOR_ACTION), + onTap: () { + widget.item.openLink(); + }, + )); } if (stockShowTests || (widget.item.testResultCount > 0)) { - tiles.add( - ListTile( - title: Text(L10().testResults), - leading: Icon(TablerIcons.list_check, color: COLOR_ACTION), - trailing: Text("${widget.item.testResultCount}"), - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StockItemTestResultsWidget(widget.item)) - ).then((ctx) { - refresh(context); - }); - } - ) - ); + tiles.add(ListTile( + title: Text(L10().testResults), + leading: Icon(TablerIcons.list_check, color: COLOR_ACTION), + trailing: Text("${widget.item.testResultCount}"), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + StockItemTestResultsWidget(widget.item))).then((ctx) { + refresh(context); + }); + })); } if (widget.item.hasPurchasePrice) { - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().purchasePrice), leading: Icon(TablerIcons.currency_dollar), - trailing: Text( - renderCurrency(widget.item.purchasePrice, widget.item.purchasePriceCurrency) - ) - ) - ); + trailing: Text(renderCurrency( + widget.item.purchasePrice, widget.item.purchasePriceCurrency)))); } // TODO - Is this stock item linked to a PurchaseOrder? if (stockShowHistory && widget.item.trackingItemCount > 0) { - tiles.add( - ListTile( - title: Text(L10().history), - leading: Icon(TablerIcons.history, color: COLOR_ACTION), - trailing: Text("${widget.item.trackingItemCount}"), - onTap: () { - Navigator.push( + tiles.add(ListTile( + title: Text(L10().history), + leading: Icon(TablerIcons.history, color: COLOR_ACTION), + trailing: Text("${widget.item.trackingItemCount}"), + onTap: () { + Navigator.push( context, MaterialPageRoute( - builder: (context) => StockItemHistoryWidget(widget.item)) - ).then((ctx) { - refresh(context); - }); - }, - ) - ); + builder: (context) => + StockItemHistoryWidget(widget.item))).then((ctx) { + refresh(context); + }); + }, + )); } // Notes field - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().notes), leading: Icon(TablerIcons.note, color: COLOR_ACTION), onTap: () { Navigator.push( - context, - MaterialPageRoute(builder: (context) => NotesWidget(widget.item)) - ); - } - ) - ); - - tiles.add( - ListTile( - title: Text(L10().attachments), - leading: Icon(TablerIcons.file, color: COLOR_ACTION), - trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, - onTap: () { - Navigator.push( context, MaterialPageRoute( + builder: (context) => NotesWidget(widget.item))); + })); + + tiles.add(ListTile( + title: Text(L10().attachments), + leading: Icon(TablerIcons.file, color: COLOR_ACTION), + trailing: attachmentCount > 0 ? Text(attachmentCount.toString()) : null, + onTap: () { + Navigator.push( + context, + MaterialPageRoute( builder: (context) => AttachmentWidget( - InvenTreeStockItemAttachment(), - widget.item.pk, - L10().stockItem, - widget.item.canEdit, - ) - ) - ); - }, - ) - ); + InvenTreeStockItemAttachment(), + widget.item.pk, + L10().stockItem, + widget.item.canEdit, + ))); + }, + )); return tiles; } - -} \ No newline at end of file +} diff --git a/lib/widget/stock/stock_item_history.dart b/lib/widget/stock/stock_item_history.dart index b2829fd2..ce2601fc 100644 --- a/lib/widget/stock/stock_item_history.dart +++ b/lib/widget/stock/stock_item_history.dart @@ -14,10 +14,12 @@ class StockItemHistoryWidget extends StatefulWidget { final InvenTreeStockItem item; @override - _StockItemHistoryDisplayState createState() => _StockItemHistoryDisplayState(item); + _StockItemHistoryDisplayState createState() => + _StockItemHistoryDisplayState(item); } -class _StockItemHistoryDisplayState extends RefreshableState { +class _StockItemHistoryDisplayState + extends RefreshableState { _StockItemHistoryDisplayState(this.item); final InvenTreeStockItem item; @@ -36,14 +38,14 @@ class _StockItemHistoryDisplayState extends RefreshableState filters) : super(filters: filters); + const PaginatedStockHistoryList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().stockItemHistory; @@ -75,7 +77,8 @@ class _PaginatedStockHistoryState int limit, int offset, Map params) async { await InvenTreeAPI().StockHistoryStatus.load(); - final page = await InvenTreeStockItemHistory().listPaginated(limit, offset, filters: params); + final page = await InvenTreeStockItemHistory() + .listPaginated(limit, offset, filters: params); return page; } diff --git a/lib/widget/stock/stock_item_test_results.dart b/lib/widget/stock/stock_item_test_results.dart index d1671e57..646c76eb 100644 --- a/lib/widget/stock/stock_item_test_results.dart +++ b/lib/widget/stock/stock_item_test_results.dart @@ -13,20 +13,18 @@ import "package:inventree/inventree/model.dart"; import "package:inventree/widget/progress.dart"; import "package:inventree/widget/refreshable_state.dart"; - class StockItemTestResultsWidget extends StatefulWidget { - const StockItemTestResultsWidget(this.item, {Key? key}) : super(key: key); final InvenTreeStockItem item; @override - _StockItemTestResultDisplayState createState() => _StockItemTestResultDisplayState(item); + _StockItemTestResultDisplayState createState() => + _StockItemTestResultDisplayState(item); } - -class _StockItemTestResultDisplayState extends RefreshableState { - +class _StockItemTestResultDisplayState + extends RefreshableState { _StockItemTestResultDisplayState(this.item); @override @@ -40,15 +38,12 @@ class _StockItemTestResultDisplayState extends RefreshableState actions = []; if (InvenTreeStockItemTestResult().canCreate) { - actions.add( - SpeedDialChild( + actions.add(SpeedDialChild( child: Icon(TablerIcons.circle_plus), label: L10().testResultAdd, onTap: () { addTestResult(context); - } - ) - ); + })); } return actions; @@ -62,9 +57,16 @@ class _StockItemTestResultDisplayState extends RefreshableState addTestResult(BuildContext context, {int templateId = 0, String name = "", bool nameIsEditable = true, bool result = false, String value = "", bool valueRequired = false, bool attachmentRequired = false}) async { - - Map> fields = InvenTreeStockItemTestResult().formFields(); + Future addTestResult(BuildContext context, + {int templateId = 0, + String name = "", + bool nameIsEditable = true, + bool result = false, + String value = "", + bool valueRequired = false, + bool attachmentRequired = false}) async { + Map> fields = + InvenTreeStockItemTestResult().formFields(); // Add additional filters fields["template"]?["filters"]?["part"] = "${item.partId}"; @@ -102,7 +104,6 @@ class _StockItemTestResultDisplayState extends RefreshableState getTiles(BuildContext context) { List tiles = []; - tiles.add( - Card( + tiles.add(Card( child: ListTile( - title: Text(item.partName), - subtitle: Text(item.partDescription), - leading: InvenTreeAPI().getThumbnail(item.partImage), - ) - ) - ); + title: Text(item.partName), + subtitle: Text(item.partDescription), + leading: InvenTreeAPI().getThumbnail(item.partImage), + ))); - tiles.add( - ListTile( + tiles.add(ListTile( title: Text(L10().testResults, - style: TextStyle(fontWeight: FontWeight.bold) - ) - ) - ); + style: TextStyle(fontWeight: FontWeight.bold)))); if (loading) { tiles.add(progressIndicator()); @@ -172,7 +166,6 @@ class _StockItemTestResultDisplayState extends RefreshableState filters; @@ -18,9 +16,7 @@ class StockItemList extends StatefulWidget { _StockListState createState() => _StockListState(filters); } - class _StockListState extends RefreshableState { - _StockListState(this.filters); final Map filters; @@ -35,20 +31,18 @@ class _StockListState extends RefreshableState { } class PaginatedStockItemList extends PaginatedSearchWidget { - - const PaginatedStockItemList(Map filters) : super(filters: filters); + const PaginatedStockItemList(Map filters) + : super(filters: filters); @override String get searchTitle => L10().stockItems; @override _PaginatedStockItemListState createState() => _PaginatedStockItemListState(); - } - -class _PaginatedStockItemListState extends PaginatedSearchState { - +class _PaginatedStockItemListState + extends PaginatedSearchState { _PaginatedStockItemListState() : super(); @override @@ -56,14 +50,14 @@ class _PaginatedStockItemListState extends PaginatedSearchState get orderingOptions => { - "part__name": L10().name, - "part__IPN": L10().internalPartNumber, - "stock": L10().quantity, - "status": L10().status, - "batch": L10().batchCode, - "updated": L10().lastUpdated, - "stocktake_date": L10().lastStocktake, - }; + "part__name": L10().name, + "part__IPN": L10().internalPartNumber, + "stock": L10().quantity, + "status": L10().status, + "batch": L10().batchCode, + "updated": L10().lastUpdated, + "stocktake_date": L10().lastStocktake, + }; @override Map> get filterOptions { @@ -111,23 +105,19 @@ class _PaginatedStockItemListState extends PaginatedSearchState requestPage(int limit, int offset, Map params) async { - + Future requestPage( + int limit, int offset, Map params) async { // Ensure StockStatus codes are loaded await InvenTreeAPI().StockStatus.load(); - final page = await InvenTreeStockItem().listPaginated( - limit, - offset, - filters: params - ); + final page = await InvenTreeStockItem() + .listPaginated(limit, offset, filters: params); return page; } @override Widget buildItem(BuildContext context, InvenTreeModel model) { - InvenTreeStockItem item = model as InvenTreeStockItem; return ListTile( @@ -135,18 +125,18 @@ class _PaginatedStockItemListState extends PaginatedSearchState fetchProfileToken({ - UserProfile? profile, - String username = testUsername, - String password = testPassword -}) async { - +Future fetchProfileToken( + {UserProfile? profile, + String username = testUsername, + String password = testPassword}) async { profile ??= await UserProfileDBManager().getProfileByName(testServerName); assert(profile != null); - final response = await InvenTreeAPI().fetchToken(profile!, username, password); + final response = + await InvenTreeAPI().fetchToken(profile!, username, password); return response.successful(); } - /* * Setup a valid profile, and return it */ -Future setupServerProfile({bool select = true, bool fetchToken = false}) async { +Future setupServerProfile( + {bool select = true, bool fetchToken = false}) async { // Setup a valid server profile - UserProfile? profile = await UserProfileDBManager().getProfileByName(testServerName); + UserProfile? profile = + await UserProfileDBManager().getProfileByName(testServerName); if (profile == null) { // Profile does not already exist - create it! bool result = await UserProfileDBManager().addProfile( - UserProfile( - server: testServerAddress, - name: testServerName - ) - ); + UserProfile(server: testServerAddress, name: testServerName)); assert(result); } @@ -84,15 +79,13 @@ Future setupServerProfile({bool select = true, bool fetchToken = fa return profile!; } - /* * Complete all steps necessary to login to the server */ Future connectToTestServer() async { - // Setup profile, and fetch user token as necessary final profile = await setupServerProfile(fetchToken: true); // Connect to the server assert(await InvenTreeAPI().connectToServer(profile)); -} \ No newline at end of file +} diff --git a/test/user_profile_test.dart b/test/user_profile_test.dart index 0921d761..a8fd2599 100644 --- a/test/user_profile_test.dart +++ b/test/user_profile_test.dart @@ -56,21 +56,16 @@ void main() { // 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() - ); + bool result = await UserProfileDBManager().addProfile(UserProfile()); expect(result, equals(false)); // Add a profile with a new name - result = await UserProfileDBManager().addProfile( - UserProfile( - name: "Another Test Profile", - ) - ); + result = await UserProfileDBManager().addProfile(UserProfile( + name: "Another Test Profile", + )); expect(result, equals(true)); @@ -81,7 +76,8 @@ void main() { }); test("Profile Name Check", () async { - bool result = await UserProfileDBManager().profileNameExists("doesnotexist"); + bool result = + await UserProfileDBManager().profileNameExists("doesnotexist"); expect(result, equals(false)); result = await UserProfileDBManager().profileNameExists("Test Server"); @@ -100,7 +96,8 @@ void main() { expect(p.name, equals(testServerName)); expect(p.server, equals(testServerAddress)); - expect(p.toString(), equals("<${p.key}> Test Server : http://localhost:8000/")); + expect(p.toString(), + equals("<${p.key}> Test Server : http://localhost:8000/")); // Test that we can update the profile p.name = "different name"; @@ -110,5 +107,4 @@ void main() { } }); }); - -} \ No newline at end of file +} diff --git a/test/wedge_scanner_test.dart b/test/wedge_scanner_test.dart index 8cdca593..7b5651d1 100644 --- a/test/wedge_scanner_test.dart +++ b/test/wedge_scanner_test.dart @@ -1,5 +1,3 @@ - - import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:flutter_test/flutter_test.dart"; @@ -7,15 +5,10 @@ import "package:inventree/barcode/barcode.dart"; import "package:inventree/barcode/wedge_controller.dart"; import "package:inventree/helpers.dart"; - void main() { testWidgets("Wedge Scanner Test", (tester) async { - await tester.pumpWidget( - MaterialApp( - home: WedgeBarcodeController(BarcodeScanHandler()) - ) - ); + MaterialApp(home: WedgeBarcodeController(BarcodeScanHandler()))); // Generate some keyboard data await simulateKeyDownEvent(LogicalKeyboardKey.keyA); @@ -27,6 +20,5 @@ void main() { debugContains("scanned: abc"); debugContains("No match for barcode"); debugContains("Server Error"); - }); -} \ No newline at end of file +}