mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +00:00 
			
		
		
		
	Merge branch 'master' into api-forms
# Conflicts: # lib/api.dart # lib/widget/part_detail.dart
This commit is contained in:
		| @@ -1,6 +1,12 @@ | ||||
| ## InvenTree App Release Notes | ||||
| --- | ||||
|  | ||||
| ### 0.2.8 - July 2021 | ||||
| --- | ||||
|  | ||||
| - Bug fixes for API calls | ||||
|  | ||||
|  | ||||
| ### 0.2.7 - July 2021 | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,8 @@ export "COCOAPODS_PARALLEL_CODE_SIGN=true" | ||||
| export "FLUTTER_TARGET=lib\main.dart" | ||||
| export "FLUTTER_BUILD_DIR=build" | ||||
| export "SYMROOT=${SOURCE_ROOT}/../build\ios" | ||||
| export "FLUTTER_BUILD_NAME=0.2.7" | ||||
| export "FLUTTER_BUILD_NUMBER=15" | ||||
| export "FLUTTER_BUILD_NAME=0.2.8" | ||||
| export "FLUTTER_BUILD_NUMBER=16" | ||||
| export "DART_OBFUSCATION=false" | ||||
| export "TRACK_WIDGET_CREATION=false" | ||||
| export "TREE_SHAKE_ICONS=false" | ||||
|   | ||||
							
								
								
									
										546
									
								
								lib/api.dart
									
									
									
									
									
								
							
							
						
						
									
										546
									
								
								lib/api.dart
									
									
									
									
									
								
							| @@ -4,19 +4,40 @@ import 'dart:io'; | ||||
|  | ||||
| import 'package:intl/intl.dart'; | ||||
|  | ||||
| import 'package:InvenTree/inventree/sentry.dart'; | ||||
| import 'package:InvenTree/user_profile.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:inventree/inventree/sentry.dart'; | ||||
| import 'package:inventree/user_profile.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:cached_network_image/cached_network_image.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:flutter_cache_manager/flutter_cache_manager.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:http/http.dart' as http; | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Class representing an API response from the server | ||||
|  */ | ||||
| class APIResponse { | ||||
|  | ||||
|   APIResponse({this.url = "", this.method = "", this.statusCode = -1, this.data}); | ||||
|  | ||||
|   int statusCode = -1; | ||||
|  | ||||
|   String url = ""; | ||||
|  | ||||
|   String method = ""; | ||||
|  | ||||
|   dynamic data; | ||||
|  | ||||
|   // Request is "valid" if a statusCode was returned | ||||
|   bool isValid() => statusCode >= 0; | ||||
| } | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Custom FileService for caching network images | ||||
|  * Requires a custom badCertificateCallback, | ||||
| @@ -214,16 +235,12 @@ class InvenTreeAPI { | ||||
|  | ||||
|     print("Connecting to ${apiUrl} -> username=${username}"); | ||||
|  | ||||
|     HttpClientResponse? response; | ||||
|     APIResponse response; | ||||
|  | ||||
|     dynamic data; | ||||
|     response = await get("", expectedStatusCode: 200); | ||||
|  | ||||
|     response = await getResponse(""); | ||||
|  | ||||
|     // Null response means something went horribly wrong! | ||||
|     // Most likely, the server cannot be contacted | ||||
|     if (response == null) { | ||||
|       // An error message has already been displayed! | ||||
|     // Response was invalid for some reason | ||||
|     if (!response.isValid()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
| @@ -232,10 +249,8 @@ class InvenTreeAPI { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     data = await responseToJson(response); | ||||
|  | ||||
|     // We expect certain response from the server | ||||
|     if (data == null || !data.containsKey("server") || !data.containsKey("version") || !data.containsKey("instance")) { | ||||
|     if (response.data == null || !response.data.containsKey("server") || !response.data.containsKey("version") || !response.data.containsKey("instance")) { | ||||
|  | ||||
|       showServerError( | ||||
|         L10().missingData, | ||||
| @@ -246,11 +261,11 @@ class InvenTreeAPI { | ||||
|     } | ||||
|  | ||||
|     // Record server information | ||||
|     _version = data["version"]; | ||||
|     instance = data['instance'] ?? ''; | ||||
|     _version = response.data["version"]; | ||||
|     instance = response.data['instance'] ?? ''; | ||||
|  | ||||
|     // Default API version is 1 if not provided | ||||
|     _apiVersion = (data['apiVersion'] ?? 1) as int; | ||||
|     _apiVersion = (response.data['apiVersion'] ?? 1) as int; | ||||
|  | ||||
|     if (_apiVersion < _minApiVersion) { | ||||
|  | ||||
| @@ -280,10 +295,10 @@ class InvenTreeAPI { | ||||
|  | ||||
|     print("Requesting token from server"); | ||||
|  | ||||
|     response = await getResponse(_URL_GET_TOKEN); | ||||
|     response = await get(_URL_GET_TOKEN); | ||||
|  | ||||
|     // A "null" response means that the request was unsuccessful | ||||
|     if (response == null) { | ||||
|     // Invalid response | ||||
|     if (!response.isValid()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
| @@ -305,9 +320,7 @@ class InvenTreeAPI { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     data = await responseToJson(response); | ||||
|  | ||||
|     if (data == null || !data.containsKey("token")) { | ||||
|     if (response.data == null || !response.data.containsKey("token")) { | ||||
|       showServerError( | ||||
|           L10().tokenMissing, | ||||
|           L10().tokenMissingFromResponse, | ||||
| @@ -317,7 +330,7 @@ class InvenTreeAPI { | ||||
|     } | ||||
|  | ||||
|     // Return the received token | ||||
|     _token = data["token"]; | ||||
|     _token = response.data["token"]; | ||||
|     print("Received token - $_token"); | ||||
|  | ||||
|     // Request user role information | ||||
| @@ -383,17 +396,15 @@ class InvenTreeAPI { | ||||
|     // Any 'older' version of the server allows any API method for any logged in user! | ||||
|     // We will return immediately, but request the user roles in the background | ||||
|  | ||||
|     var response = await get(_URL_GET_ROLES); | ||||
|     var response = await get(_URL_GET_ROLES, expectedStatusCode: 200); | ||||
|  | ||||
|     // Null response from server | ||||
|     if (response == null) { | ||||
|       print("null response requesting user roles"); | ||||
|     if (!response.isValid() || response.statusCode != 200) { | ||||
|       return; | ||||
|     } | ||||
|  | ||||
|     if (response.containsKey('roles')) { | ||||
|     if (response.data.containsKey('roles')) { | ||||
|       // Save a local copy of the user roles | ||||
|       roles = response['roles']; | ||||
|       roles = response.data['roles']; | ||||
|     } | ||||
|   } | ||||
|  | ||||
| @@ -420,108 +431,27 @@ class InvenTreeAPI { | ||||
|  | ||||
|  | ||||
|   // Perform a PATCH request | ||||
|   Future<dynamic> patch(String url, {Map<String, String> body = const {}, int expectedStatusCode=200}) async { | ||||
|     var _url = makeApiUrl(url); | ||||
|   Future<APIResponse> patch(String url, {Map<String, String> body = const {}, int expectedStatusCode=200}) async { | ||||
|     var _body = Map<String, String>(); | ||||
|  | ||||
|     // Copy across provided data | ||||
|     body.forEach((K, V) => _body[K] = V); | ||||
|  | ||||
|     print("PATCH: " + _url); | ||||
|     HttpClientRequest? request = await apiRequest(url, "PATCH"); | ||||
|  | ||||
|     final uri = Uri.parse(_url); | ||||
|  | ||||
|     // Check for invalid host | ||||
|     if (uri.host.isEmpty) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var client = createClient(true); | ||||
|  | ||||
|     HttpClientRequest? request; | ||||
|  | ||||
|     try { | ||||
|       // Open a connection to the server | ||||
|       request = await client.patchUrl(uri).timeout(Duration(seconds: 10)); | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError(L10().connectionRefused, error.toString()); | ||||
|       return null; | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|       showServerError( | ||||
|           L10().serverError, | ||||
|           error.toString() | ||||
|     if (request == null) { | ||||
|       // Return an "invalid" APIResponse | ||||
|       return new APIResponse( | ||||
|         url: url, | ||||
|         method: 'PATCH', | ||||
|       ); | ||||
|  | ||||
|       sentryReportError(error, stackTrace); | ||||
|  | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var data = json.encode(_body); | ||||
|  | ||||
|     // Set headers | ||||
|     request.headers.set(HttpHeaders.acceptHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); | ||||
|     request.headers.set(HttpHeaders.contentLengthHeader, data.length.toString()); | ||||
|     request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); | ||||
|  | ||||
|     request.add(utf8.encode(data)); | ||||
|  | ||||
|     HttpClientResponse? response; | ||||
|  | ||||
|     try { | ||||
|       response = await request.close().timeout(Duration(seconds: 10)); | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError( | ||||
|           L10().connectionRefused, | ||||
|           error.toString() | ||||
|       ); | ||||
|       return null; | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|       showServerError( | ||||
|             L10().serverError, | ||||
|             error.toString() | ||||
|       ); | ||||
|  | ||||
|       sentryReportError(error, stackTrace); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var responseData = await responseToJson(response); | ||||
|  | ||||
|     if (response.statusCode != expectedStatusCode) { | ||||
|       showStatusCodeError(response.statusCode); | ||||
|  | ||||
|       print("PATCH to ${_url} returned status code ${response.statusCode}"); | ||||
|       print("Data:"); | ||||
|       print(responseData); | ||||
|  | ||||
|       // Server error | ||||
|       if (response.statusCode >= 500) { | ||||
|         sentryReportMessage( | ||||
|             "Server error on PATCH request", | ||||
|             context: { | ||||
|               "url": _url, | ||||
|               "statusCode": "${response.statusCode}", | ||||
|               "response": responseData.toString(), | ||||
|               "request": body.toString(), | ||||
|             } | ||||
|         ); | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Include the statuscode in the response object | ||||
|     responseData["statusCode"] = response.statusCode; | ||||
|  | ||||
|     return responseData; | ||||
|     return completeRequest( | ||||
|       request, | ||||
|       data: json.encode(_body), | ||||
|       statusCode: expectedStatusCode | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   /* | ||||
| @@ -572,155 +502,42 @@ class InvenTreeAPI { | ||||
|    * We send this with the currently selected "locale", | ||||
|    * so that (hopefully) the field messages are correctly translated | ||||
|    */ | ||||
|   Future<dynamic> options(String url) async { | ||||
|   Future<APIResponse> options(String url) async { | ||||
|  | ||||
|     var _url = makeApiUrl(url); | ||||
|     HttpClientRequest? request = await apiRequest(url, "OPTIONS"); | ||||
|  | ||||
|     var client = createClient(true); | ||||
|  | ||||
|     final uri = Uri.parse(_url); | ||||
|  | ||||
|     if (uri.host.isEmpty) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     if (request == null) { | ||||
|       // Return an "invalid" APIResponse | ||||
|       return new APIResponse( | ||||
|         url: url, | ||||
|         method: 'OPTIONS' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     HttpClientRequest? request; | ||||
|     HttpClientResponse? response; | ||||
|  | ||||
|     try { | ||||
|       request = await client.openUrl("OPTIONS", uri).timeout(Duration(seconds: 10)); | ||||
|  | ||||
|       request.headers.set(HttpHeaders.acceptHeader, 'application/json'); | ||||
|       request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); | ||||
|       request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); | ||||
|       request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); | ||||
|  | ||||
|       response = await request.close().timeout(Duration(seconds: 10)); | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError(L10().connectionRefused, error.toString()); | ||||
|       return null; | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|       showServerError(L10().serverError, error.toString()); | ||||
|       sentryReportError(error, stackTrace); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var responseData = await responseToJson(response); | ||||
|  | ||||
|     return responseData; | ||||
|     return completeRequest(request); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Perform a HTTP POST request | ||||
|    * Returns a json object (or null if unsuccessful) | ||||
|    */ | ||||
|   Future<dynamic> post(String url, {Map<String, dynamic> body = const {}, int expectedStatusCode=201}) async { | ||||
|   Future<APIResponse> post(String url, {Map<String, dynamic> body = const {}, int expectedStatusCode=201}) async { | ||||
|  | ||||
|     var _url = makeApiUrl(url); | ||||
|     HttpClientRequest? request = await apiRequest(url, "POST"); | ||||
|  | ||||
|     print("POST: ${_url} -> ${body.toString()}"); | ||||
|  | ||||
|     var client = createClient(true); | ||||
|  | ||||
|     final uri = Uri.parse(_url); | ||||
|  | ||||
|     if (uri.host.isEmpty) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     if (request == null) { | ||||
|       // Return an "invalid" APIResponse | ||||
|       return new APIResponse( | ||||
|         url: url, | ||||
|         method: 'POST' | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     HttpClientRequest? request; | ||||
|  | ||||
|     try { | ||||
|       // Open a connection to the server | ||||
|       request = await client.postUrl(uri).timeout(Duration(seconds: 10)); | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError( | ||||
|           L10().connectionRefused, | ||||
|           error.toString() | ||||
|       ); | ||||
|       return null; | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|  | ||||
|       showServerError( | ||||
|         L10().serverError, | ||||
|         error.toString() | ||||
|       ); | ||||
|  | ||||
|       sentryReportError(error, stackTrace); | ||||
|  | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var data = json.encode(body); | ||||
|  | ||||
|     // Set headers | ||||
|     // Ref: https://stackoverflow.com/questions/59713003/body-not-sending-using-map-in-flutter | ||||
|     request.headers.set(HttpHeaders.acceptHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); | ||||
|     request.headers.set(HttpHeaders.contentLengthHeader, data.length.toString()); | ||||
|     request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); | ||||
|  | ||||
|     // Add JSON data to the request | ||||
|     request.add(utf8.encode(data)); | ||||
|  | ||||
|     HttpClientResponse? response; | ||||
|  | ||||
|     try { | ||||
|       response = await request.close().timeout(Duration(seconds: 10)); | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError( | ||||
|           L10().connectionRefused, | ||||
|           error.toString() | ||||
|       ); | ||||
|       return null; | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|       showServerError( | ||||
|         L10().serverError, | ||||
|         error.toString() | ||||
|       ); | ||||
|  | ||||
|       sentryReportError(error, stackTrace); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     var responseData = await responseToJson(response); | ||||
|  | ||||
|     if (response.statusCode != expectedStatusCode) { | ||||
|       showStatusCodeError(response.statusCode); | ||||
|  | ||||
|       print("POST to ${_url} returned status code ${response.statusCode}"); | ||||
|       print("Data:"); | ||||
|       print(responseData); | ||||
|  | ||||
|       // Server error | ||||
|       if (response.statusCode >= 500) { | ||||
|         sentryReportMessage( | ||||
|           "Server error on POST request", | ||||
|           context: { | ||||
|             "url": _url, | ||||
|             "statusCode": "${response.statusCode}", | ||||
|             "response": responseData.toString(), | ||||
|             "request": body.toString(), | ||||
|           } | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     return responseData; | ||||
|     return completeRequest( | ||||
|       request, | ||||
|       data: json.encode(body), | ||||
|       statusCode: expectedStatusCode | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   HttpClient createClient(bool allowBadCert) { | ||||
| @@ -750,20 +567,21 @@ class InvenTreeAPI { | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Perform a HTTP GET request, | ||||
|    * and return the Response object | ||||
|    * (or null if the request fails) | ||||
|    * Initiate a HTTP request to the server | ||||
|    * | ||||
|    * @param url is the API endpoint | ||||
|    * @param method is the HTTP method e.g. 'POST' / 'PATCH' / 'GET' etc; | ||||
|    * @param params is the request parameters | ||||
|    */ | ||||
|   Future<HttpClientResponse?> getResponse(String url, {Map<String, String> params = const {}}) async { | ||||
|   Future<HttpClientRequest?> apiRequest(String url, String method, {Map<String, String> urlParams = const {}}) async { | ||||
|  | ||||
|     var _url = makeApiUrl(url); | ||||
|  | ||||
|     print("GET: ${_url}"); | ||||
|     // Add any required query parameters to the URL using ?key=value notation | ||||
|     if (urlParams.isNotEmpty) { | ||||
|       String query = "?"; | ||||
|  | ||||
|     // If query parameters are supplied, form a query string | ||||
|     if (params.isNotEmpty) { | ||||
|       String query = '?'; | ||||
|  | ||||
|       params.forEach((K, V) => query += K + '=' + V + '&'); | ||||
|       urlParams.forEach((k, v) => query += "${k}=${v}&"); | ||||
|  | ||||
|       _url += query; | ||||
|     } | ||||
| @@ -773,70 +591,106 @@ class InvenTreeAPI { | ||||
|       _url = _url.substring(0, _url.length - 1); | ||||
|     } | ||||
|  | ||||
|     Uri? _uri = Uri.tryParse(_url); | ||||
|  | ||||
|     print("apiRequest ${method} -> ${url}"); | ||||
|  | ||||
|     if (_uri == null) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     if (_uri.host.isEmpty) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     HttpClientRequest? _request; | ||||
|  | ||||
|     var client = createClient(true); | ||||
|  | ||||
|     Uri? uri = Uri.tryParse(_url); | ||||
|  | ||||
|     if (uri == null) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     // Check for invalid host | ||||
|     if (uri.host.isEmpty) { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     HttpClientRequest? request; | ||||
|  | ||||
|     // Attempt to open a connection to the server | ||||
|     try { | ||||
|       // Open a connection | ||||
|       request = await client.getUrl(uri).timeout(Duration(seconds: 10)); | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|       _request = await client.openUrl(method, _uri).timeout(Duration(seconds: 10)); | ||||
|  | ||||
|       // Set headers | ||||
|       _request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); | ||||
|       _request.headers.set(HttpHeaders.acceptHeader, 'application/json'); | ||||
|       _request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); | ||||
|       _request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); | ||||
|  | ||||
|       return _request; | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError(L10().connectionRefused, error.toString()); | ||||
|       return null; | ||||
|     } on FormatException { | ||||
|       showServerError(L10().invalidHost, L10().invalidHostDetails); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|         sentryReportError(error, stackTrace); | ||||
|         showServerError(L10().serverError, error.toString()); | ||||
|         return null; | ||||
|     } | ||||
|  | ||||
|     // Set connection headers | ||||
|     request.headers.set(HttpHeaders.acceptHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.contentTypeHeader, 'application/json'); | ||||
|     request.headers.set(HttpHeaders.acceptLanguageHeader, Intl.getCurrentLocale()); | ||||
|     request.headers.set(HttpHeaders.authorizationHeader, _authorizationHeader()); | ||||
|  | ||||
|     try { | ||||
|       HttpClientResponse response = await request.close().timeout(Duration(seconds: 10)); | ||||
|       return response; | ||||
|  | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|       return null; | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError(L10().connectionRefused, error.toString()); | ||||
|       return null; | ||||
|     } catch (error, stackTrace) { | ||||
|  | ||||
|         showServerError( | ||||
|             L10().serverError, | ||||
|             error.toString() | ||||
|         ); | ||||
|  | ||||
|         sentryReportError(error, stackTrace); | ||||
|  | ||||
|       showServerError(L10().serverError, error.toString()); | ||||
|       sentryReportError(error, stackTrace); | ||||
|       return null; | ||||
|     } | ||||
|   } | ||||
|  | ||||
|  | ||||
|   /** | ||||
|    * Complete an API request, and return an APIResponse object | ||||
|    */ | ||||
|   Future<APIResponse> completeRequest(HttpClientRequest request, {String? data, int? statusCode}) async { | ||||
|  | ||||
|     if (data != null && data.isNotEmpty) { | ||||
|       request.headers.set(HttpHeaders.contentLengthHeader, data.length.toString()); | ||||
|       request.add(utf8.encode(data)); | ||||
|     } | ||||
|  | ||||
|     APIResponse response = new APIResponse( | ||||
|       method: request.method, | ||||
|       url: request.uri.toString() | ||||
|     ); | ||||
|  | ||||
|     try { | ||||
|       HttpClientResponse? _response = await request.close().timeout(Duration(seconds: 10)); | ||||
|  | ||||
|       response.statusCode = _response.statusCode; | ||||
|       response.data = await responseToJson(_response); | ||||
|  | ||||
|       // Expected status code not returned | ||||
|       if ((statusCode != null) && (statusCode != _response.statusCode)) { | ||||
|         showStatusCodeError(_response.statusCode); | ||||
|       } | ||||
|  | ||||
|       // Report any server errors | ||||
|       if (_response.statusCode >= 500) { | ||||
|         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(), | ||||
|           } | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|     } on SocketException catch (error) { | ||||
|       showServerError(L10().connectionRefused, error.toString()); | ||||
|     } on TimeoutException { | ||||
|       showTimeoutError(); | ||||
|     } catch (error, stackTrace) { | ||||
|       showServerError(L10().serverError, error.toString()); | ||||
|       sentryReportError(error, stackTrace); | ||||
|     } | ||||
|  | ||||
|     return response; | ||||
|  | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Convert a HttpClientResponse response object to JSON | ||||
|    */ | ||||
|   dynamic responseToJson(HttpClientResponse response) async { | ||||
|  | ||||
|     String body = await response.transform(utf8.decoder).join(); | ||||
| @@ -872,55 +726,37 @@ class InvenTreeAPI { | ||||
|    * Perform a HTTP GET request | ||||
|    * Returns a json object (or null if did not complete) | ||||
|    */ | ||||
|   Future<dynamic> get(String url, {Map<String, String> params = const {}, int expectedStatusCode=200}) async { | ||||
|   Future<APIResponse> get(String url, {Map<String, String> params = const {}, int expectedStatusCode=200}) async { | ||||
|  | ||||
|     var response = await getResponse(url, params: params); | ||||
|     HttpClientRequest? request = await apiRequest( | ||||
|       url, | ||||
|       "GET", | ||||
|       urlParams: params, | ||||
|     ); | ||||
|  | ||||
|     // A null response means something has gone wrong... | ||||
|     if (response == null) { | ||||
|       print("null response from GET ${url}"); | ||||
|       return null; | ||||
|     if (request == null) { | ||||
|       // Return an "invalid" APIResponse | ||||
|       return new APIResponse( | ||||
|         url: url, | ||||
|         method: 'GET', | ||||
|       ); | ||||
|     } | ||||
|  | ||||
|     var responseData = await responseToJson(response); | ||||
|  | ||||
|     // Check the status code of the response | ||||
|     if (response.statusCode != expectedStatusCode) { | ||||
|       showStatusCodeError(response.statusCode); | ||||
|  | ||||
|       // Server error | ||||
|       if (response.statusCode >= 500) { | ||||
|         sentryReportMessage( | ||||
|             "Server error on GET request", | ||||
|             context: { | ||||
|               "url": url, | ||||
|               "statusCode": "${response.statusCode}", | ||||
|               "response": responseData.toString(), | ||||
|               "params": params.toString(), | ||||
|             } | ||||
|         ); | ||||
|       } | ||||
|  | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     return responseData; | ||||
|     return completeRequest(request); | ||||
|   } | ||||
|  | ||||
|   // Return a list of request headers | ||||
|   Map<String, String> defaultHeaders() { | ||||
|     var headers = Map<String, String>(); | ||||
|  | ||||
|     headers[HttpHeaders.authorizationHeader] = _authorizationHeader(); | ||||
|     headers[HttpHeaders.acceptHeader] = 'application/json'; | ||||
|     headers[HttpHeaders.contentTypeHeader] = 'application/json'; | ||||
|     headers[HttpHeaders.acceptLanguageHeader] = Intl.getCurrentLocale(); | ||||
|  | ||||
|     return headers; | ||||
|   } | ||||
|  | ||||
|   Map<String, String> jsonHeaders() { | ||||
|     var headers = defaultHeaders(); | ||||
|     headers['Content-Type'] = 'application/json'; | ||||
|     return headers; | ||||
|   } | ||||
|  | ||||
|   String _authorizationHeader() { | ||||
|     if (_token.isNotEmpty) { | ||||
|       return "Token $_token"; | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
|  */ | ||||
|  | ||||
| import 'package:sembast/sembast.dart'; | ||||
| import 'package:InvenTree/preferences.dart'; | ||||
| import 'package:inventree/preferences.dart'; | ||||
|  | ||||
| class InvenTreeSettingsManager { | ||||
|  | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import 'package:InvenTree/app_settings.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:inventree/app_settings.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:audioplayers/audioplayers.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -9,15 +9,15 @@ import 'package:one_context/one_context.dart'; | ||||
|  | ||||
| import 'package:qr_code_scanner/qr_code_scanner.dart'; | ||||
|  | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
| import 'package:InvenTree/widget/location_display.dart'; | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/stock_detail.dart'; | ||||
| import 'package:inventree/widget/location_display.dart'; | ||||
| import 'package:inventree/widget/part_detail.dart'; | ||||
| import 'package:inventree/widget/stock_detail.dart'; | ||||
|  | ||||
| import 'dart:io'; | ||||
|  | ||||
| @@ -92,7 +92,7 @@ class BarcodeHandler { | ||||
|  | ||||
|       print("Scanned barcode data: ${barcode}"); | ||||
|  | ||||
|       var data = await InvenTreeAPI().post( | ||||
|       var response = await InvenTreeAPI().post( | ||||
|           url, | ||||
|           body: { | ||||
|             "barcode": barcode, | ||||
| @@ -100,19 +100,19 @@ class BarcodeHandler { | ||||
|           expectedStatusCode: 200 | ||||
|       ); | ||||
|  | ||||
|       if (data == null) { | ||||
|       if (!response.isValid()) { | ||||
|         return; | ||||
|       } | ||||
|  | ||||
|       if (data.containsKey('error')) { | ||||
|       if (response.data.containsKey('error')) { | ||||
|         _controller?.resumeCamera(); | ||||
|         onBarcodeUnknown(data); | ||||
|       } else if (data.containsKey('success')) { | ||||
|         onBarcodeUnknown(response.data); | ||||
|       } else if (response.data.containsKey('success')) { | ||||
|         _controller?.resumeCamera(); | ||||
|         onBarcodeMatched(data); | ||||
|         onBarcodeMatched(response.data); | ||||
|       } else { | ||||
|         _controller?.resumeCamera(); | ||||
|         onBarcodeUnhandled(data); | ||||
|         onBarcodeUnhandled(response.data); | ||||
|       } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
|  | ||||
| @@ -149,13 +149,13 @@ class InvenTreeModel { | ||||
|    */ | ||||
|   Future<bool> reload() async { | ||||
|  | ||||
|     var response = await api.get(url, params: defaultGetFilters()); | ||||
|     var response = await api.get(url, params: defaultGetFilters(), expectedStatusCode: 200); | ||||
|      | ||||
|     if (response == null) { | ||||
|     if (!response.isValid()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     jsondata = response; | ||||
|     jsondata = response.data; | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
| @@ -163,19 +163,21 @@ class InvenTreeModel { | ||||
|   // POST data to update the model | ||||
|   Future<bool> update({Map<String, String> values = const {}}) async { | ||||
|  | ||||
|     var addr = path.join(URL, pk.toString()); | ||||
|     var url = path.join(URL, pk.toString()); | ||||
|  | ||||
|     if (!addr.endsWith("/")) { | ||||
|       addr += "/"; | ||||
|     if (!url.endsWith("/")) { | ||||
|       url += "/"; | ||||
|     } | ||||
|  | ||||
|     var response = await api.patch( | ||||
|       addr, | ||||
|       url, | ||||
|       body: values, | ||||
|       expectedStatusCode: 200 | ||||
|     ); | ||||
|  | ||||
|     if (response == null) return false; | ||||
|     if (!response.isValid()) { | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     return true; | ||||
|   } | ||||
| @@ -199,15 +201,13 @@ class InvenTreeModel { | ||||
|       params[key] = filters[key] ?? ''; | ||||
|     } | ||||
|  | ||||
|     print("GET: $url ${params.toString()}"); | ||||
|  | ||||
|     var response = await api.get(url, params: params); | ||||
|  | ||||
|     if (response == null) { | ||||
|     if (!response.isValid()) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     return createFromJson(response); | ||||
|     return createFromJson(response.data); | ||||
|   } | ||||
|  | ||||
|   Future<InvenTreeModel?> create(Map<String, dynamic> data) async { | ||||
| @@ -224,11 +224,12 @@ class InvenTreeModel { | ||||
|  | ||||
|     var response = await api.post(URL, body: data); | ||||
|  | ||||
|     if (response == null) { | ||||
|     // Invalid response returned from server | ||||
|     if (!response.isValid()) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     return createFromJson(response); | ||||
|     return createFromJson(response.data); | ||||
|   } | ||||
|  | ||||
|   Future<InvenTreePageResponse?> listPaginated(int limit, int offset, {Map<String, String> filters = const {}}) async { | ||||
| @@ -243,19 +244,19 @@ class InvenTreeModel { | ||||
|  | ||||
|     var response = await api.get(URL, params: params); | ||||
|  | ||||
|     if (response == null) { | ||||
|     if (!response.isValid()) { | ||||
|       return null; | ||||
|     } | ||||
|  | ||||
|     // Construct the response | ||||
|     InvenTreePageResponse page = new InvenTreePageResponse(); | ||||
|  | ||||
|     if (response.containsKey("count") && response.containsKey("results")) { | ||||
|        page.count = response["count"] as int; | ||||
|     if (response.data.containsKey("count") && response.data.containsKey("results")) { | ||||
|        page.count = response.data["count"] as int; | ||||
|  | ||||
|        page.results = []; | ||||
|  | ||||
|        for (var result in response["results"]) { | ||||
|        for (var result in response.data["results"]) { | ||||
|          page.addResult(createFromJson(result)); | ||||
|        } | ||||
|  | ||||
| @@ -282,7 +283,7 @@ class InvenTreeModel { | ||||
|     // A list of "InvenTreeModel" items | ||||
|     List<InvenTreeModel> results = []; | ||||
|  | ||||
|     if (response == null) { | ||||
|     if (!response.isValid()) { | ||||
|       return results; | ||||
|     } | ||||
|  | ||||
| @@ -290,7 +291,7 @@ class InvenTreeModel { | ||||
|     // - No data receieved | ||||
|     // - Data is not a list of maps | ||||
|  | ||||
|     for (var d in response) { | ||||
|     for (var d in response.data) { | ||||
|  | ||||
|       // Create a new object (of the current class type | ||||
|       InvenTreeModel obj = createFromJson(d); | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'model.dart'; | ||||
| import 'dart:io'; | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import 'package:device_info/device_info.dart'; | ||||
| import 'package:package_info/package_info.dart'; | ||||
| import 'package:sentry_flutter/sentry_flutter.dart'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
| Future<Map<String, dynamic>> getDeviceInfo() async { | ||||
|  | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:http/http.dart' as http; | ||||
| import 'model.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
|  | ||||
| class InvenTreeStockItemTestResult extends InvenTreeModel { | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import 'dart:async'; | ||||
|  | ||||
| import 'package:InvenTree/inventree/sentry.dart'; | ||||
| import 'package:inventree/inventree/sentry.dart'; | ||||
| import 'package:flutter_localizations/flutter_localizations.dart'; | ||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||
|  | ||||
| import 'package:InvenTree/widget/home.dart'; | ||||
| import 'package:inventree/widget/home.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:one_context/one_context.dart'; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/settings/release.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/settings/release.dart'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @@ -7,7 +7,7 @@ import 'package:flutter/services.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:package_info/package_info.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| class InvenTreeAboutWidget extends StatelessWidget { | ||||
|  | ||||
|   | ||||
| @@ -2,11 +2,11 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| import 'package:InvenTree/app_settings.dart'; | ||||
| import 'package:inventree/app_settings.dart'; | ||||
|  | ||||
| class InvenTreeAppSettingsWidget extends StatefulWidget { | ||||
|   @override | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/spinner.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/spinner.dart'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import '../api.dart'; | ||||
| import '../user_profile.dart'; | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter_markdown/flutter_markdown.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| class ReleaseNotesWidget extends StatelessWidget { | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
| import 'package:InvenTree/inventree/sentry.dart'; | ||||
| import 'package:InvenTree/settings/about.dart'; | ||||
| import 'package:InvenTree/settings/app_settings.dart'; | ||||
| import 'package:InvenTree/settings/login.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:inventree/inventree/sentry.dart'; | ||||
| import 'package:inventree/settings/about.dart'; | ||||
| import 'package:inventree/settings/app_settings.dart'; | ||||
| import 'package:inventree/settings/login.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:url_launcher/url_launcher.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/app_settings.dart'; | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/inventree/sentry.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/app_settings.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/inventree/sentry.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:InvenTree/widget/paginator.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:inventree/widget/part_detail.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/widget/paginator.dart'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/inventree/company.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/inventree/company.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| class CompanyDetailWidget extends StatefulWidget { | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
|  | ||||
| import 'package:InvenTree/widget/company_detail.dart'; | ||||
| import 'package:inventree/widget/company_detail.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/inventree/company.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/inventree/company.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
|  | ||||
| abstract class CompanyListWidget extends StatefulWidget { | ||||
|  | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
|  | ||||
| import 'package:InvenTree/app_settings.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:inventree/app_settings.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:audioplayers/audioplayers.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
| import 'package:one_context/one_context.dart'; | ||||
|  | ||||
| Future<void> confirmationDialog(String title, String text, {String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async { | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/barcode.dart'; | ||||
| import 'package:InvenTree/widget/company_list.dart'; | ||||
| import 'package:InvenTree/widget/search.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/barcode.dart'; | ||||
| import 'package:inventree/widget/company_list.dart'; | ||||
| import 'package:inventree/widget/search.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:InvenTree/widget/category_display.dart'; | ||||
| import 'package:InvenTree/widget/location_display.dart'; | ||||
| import 'package:inventree/widget/category_display.dart'; | ||||
| import 'package:inventree/widget/location_display.dart'; | ||||
|  | ||||
| import 'package:InvenTree/settings/settings.dart'; | ||||
| import 'package:inventree/settings/settings.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| class InvenTreeDrawer extends StatelessWidget { | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:image_picker/image_picker.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'dart:async'; | ||||
| import 'dart:io'; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:InvenTree/user_profile.dart'; | ||||
| import 'package:inventree/user_profile.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| @@ -6,17 +6,17 @@ import 'file:///C:/inventree-app/lib/l10.dart'; | ||||
|  | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| import 'package:InvenTree/barcode.dart'; | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/barcode.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
| import 'package:InvenTree/settings/login.dart'; | ||||
| import 'package:inventree/settings/login.dart'; | ||||
|  | ||||
| import 'package:InvenTree/widget/category_display.dart'; | ||||
| import 'package:InvenTree/widget/company_list.dart'; | ||||
| import 'package:InvenTree/widget/location_display.dart'; | ||||
| import 'package:InvenTree/widget/search.dart'; | ||||
| import 'package:InvenTree/widget/spinner.dart'; | ||||
| import 'package:InvenTree/widget/drawer.dart'; | ||||
| import 'package:inventree/widget/category_display.dart'; | ||||
| import 'package:inventree/widget/company_list.dart'; | ||||
| import 'package:inventree/widget/location_display.dart'; | ||||
| import 'package:inventree/widget/search.dart'; | ||||
| import 'package:inventree/widget/spinner.dart'; | ||||
| import 'package:inventree/widget/drawer.dart'; | ||||
|  | ||||
| class InvenTreeHomePage extends StatefulWidget { | ||||
|   InvenTreeHomePage({Key? key}) : super(key: key); | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/app_settings.dart'; | ||||
| import 'package:InvenTree/barcode.dart'; | ||||
| import 'package:InvenTree/inventree/sentry.dart'; | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/app_settings.dart'; | ||||
| import 'package:inventree/barcode.dart'; | ||||
| import 'package:inventree/inventree/sentry.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
|  | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/stock_detail.dart'; | ||||
| import 'package:InvenTree/widget/paginator.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:inventree/widget/stock_detail.dart'; | ||||
| import 'package:inventree/widget/paginator.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| class PaginatedSearchWidget extends StatelessWidget { | ||||
|   | ||||
| @@ -7,16 +7,16 @@ import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/foundation.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/widget/full_screen_image.dart'; | ||||
| import 'package:InvenTree/widget/category_display.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/widget/full_screen_image.dart'; | ||||
| import 'package:inventree/widget/category_display.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
|  | ||||
| import 'location_display.dart'; | ||||
|  | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter_markdown/flutter_markdown.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| class PartNotesWidget extends StatefulWidget { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| import 'package:InvenTree/widget/drawer.dart'; | ||||
| import 'package:inventree/widget/drawer.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
|   | ||||
| @@ -1,15 +1,15 @@ | ||||
|  | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/stock_detail.dart'; | ||||
| import 'package:inventree/widget/part_detail.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:inventree/widget/stock_detail.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
|  | ||||
| import '../api.dart'; | ||||
|  | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
| import 'package:one_context/one_context.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) { | ||||
|   | ||||
| @@ -1,13 +1,13 @@ | ||||
|  | ||||
|  | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/widget/part_detail.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import '../api.dart'; | ||||
|  | ||||
| @@ -35,11 +35,9 @@ class _StarredPartState extends RefreshableState<StarredPartWidget> { | ||||
|  | ||||
|     starredParts.clear(); | ||||
|  | ||||
|     if (parts != null) { | ||||
|       for (int idx = 0; idx < parts.length; idx++) { | ||||
|         if (parts[idx] is InvenTreePart) { | ||||
|           starredParts.add(parts[idx] as InvenTreePart); | ||||
|         } | ||||
|     for (int idx = 0; idx < parts.length; idx++) { | ||||
|       if (parts[idx] is InvenTreePart) { | ||||
|         starredParts.add(parts[idx] as InvenTreePart); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -1,24 +1,21 @@ | ||||
|  | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:InvenTree/barcode.dart'; | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/location_display.dart'; | ||||
| import 'package:InvenTree/widget/part_detail.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:InvenTree/widget/stock_item_test_results.dart'; | ||||
| import 'package:InvenTree/widget/stock_notes.dart'; | ||||
| import 'package:inventree/barcode.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/location_display.dart'; | ||||
| import 'package:inventree/widget/part_detail.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
| import 'package:inventree/widget/stock_item_test_results.dart'; | ||||
| import 'package:inventree/widget/stock_notes.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
|  | ||||
| import 'package:flutter_typeahead/flutter_typeahead.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|   | ||||
| @@ -1,19 +1,19 @@ | ||||
| import 'package:InvenTree/inventree/part.dart'; | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:InvenTree/inventree/model.dart'; | ||||
| import 'package:InvenTree/api.dart'; | ||||
| import 'package:InvenTree/widget/dialogs.dart'; | ||||
| import 'package:InvenTree/widget/fields.dart'; | ||||
| import 'package:InvenTree/widget/progress.dart'; | ||||
| import 'package:InvenTree/widget/snacks.dart'; | ||||
| import 'package:inventree/inventree/part.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:inventree/inventree/model.dart'; | ||||
| import 'package:inventree/api.dart'; | ||||
| import 'package:inventree/widget/dialogs.dart'; | ||||
| import 'package:inventree/widget/fields.dart'; | ||||
| import 'package:inventree/widget/progress.dart'; | ||||
| import 'package:inventree/widget/snacks.dart'; | ||||
|  | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
| import 'dart:io'; | ||||
|  | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,9 @@ | ||||
|  | ||||
| import 'package:InvenTree/inventree/stock.dart'; | ||||
| import 'package:InvenTree/widget/refreshable_state.dart'; | ||||
| import 'package:inventree/inventree/stock.dart'; | ||||
| import 'package:inventree/widget/refreshable_state.dart'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter_markdown/flutter_markdown.dart'; | ||||
| import 'package:InvenTree/l10.dart'; | ||||
| import 'package:inventree/l10.dart'; | ||||
|  | ||||
|  | ||||
| class StockNotesWidget extends StatefulWidget { | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| name: InvenTree | ||||
| name: inventree | ||||
| description: InvenTree stock management | ||||
|  | ||||
| # In Android, build-name is used as versionName while build-number used as versionCode. | ||||
| @@ -7,7 +7,7 @@ description: InvenTree stock management | ||||
| # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. | ||||
| # Read more about iOS versioning at | ||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||
| version: 0.2.7+15 | ||||
| version: 0.2.8+16 | ||||
|  | ||||
| environment: | ||||
|   sdk: ">=2.12.0 <3.0.0" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user