mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-30 21:05:42 +00:00 
			
		
		
		
	Cleanup for API error handling
Ref: https://stackoverflow.com/questions/54617432/looking-up-a-deactivated-widgets-ancestor-is-unsafe
This commit is contained in:
		
							
								
								
									
										75
									
								
								lib/api.dart
									
									
									
									
									
								
							
							
						
						
									
										75
									
								
								lib/api.dart
									
									
									
									
									
								
							| @@ -68,15 +68,6 @@ class InvenTreeAPI { | |||||||
|   static const _URL_GET_TOKEN = "user/token/"; |   static const _URL_GET_TOKEN = "user/token/"; | ||||||
|   static const _URL_GET_VERSION = ""; |   static const _URL_GET_VERSION = ""; | ||||||
|  |  | ||||||
|   Future<void> showServerError(BuildContext context, String description) async { |  | ||||||
|     showErrorDialog( |  | ||||||
|       context, |  | ||||||
|       I18N.of(context).serverError, |  | ||||||
|       description, |  | ||||||
|       icon: FontAwesomeIcons.server |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Base URL for InvenTree API e.g. http://192.168.120.10:8000 |   // Base URL for InvenTree API e.g. http://192.168.120.10:8000 | ||||||
|   String _BASE_URL = ""; |   String _BASE_URL = ""; | ||||||
|  |  | ||||||
| @@ -209,18 +200,18 @@ class InvenTreeAPI { | |||||||
|  |  | ||||||
|     print("Connecting to ${apiUrl} -> ${username}:${password}"); |     print("Connecting to ${apiUrl} -> ${username}:${password}"); | ||||||
|  |  | ||||||
|     var response = await get("").timeout(Duration(seconds: 5)).catchError((error) { |     var response = await get("").timeout(Duration(seconds: 10)).catchError((error) { | ||||||
|  |  | ||||||
|       print("Error connecting to server: ${error.toString()}"); |       print("Error connecting to server: ${error.toString()}"); | ||||||
|  |  | ||||||
|       if (error is SocketException) { |       if (error is SocketException) { | ||||||
|         print("Error: socket exception: ${error.toString()}"); |         showServerError( | ||||||
|         // TODO - Display error dialog!! |             context, | ||||||
|         //showServerError(context, "Connection Refused"); |             I18N.of(context).connectionRefused, | ||||||
|  |             error.toString()); | ||||||
|         return null; |         return null; | ||||||
|       } else if (error is TimeoutException) { |       } else if (error is TimeoutException) { | ||||||
|         // TODO - Display timeout dialog here |         showTimeoutError(context); | ||||||
|         //showTimeoutDialog(context); |  | ||||||
|         return null; |         return null; | ||||||
|       } else { |       } else { | ||||||
|         // Unknown error type - re-throw the error and Sentry will catch it |         // Unknown error type - re-throw the error and Sentry will catch it | ||||||
| @@ -236,8 +227,9 @@ class InvenTreeAPI { | |||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       // Any status code other than 200! |       // Any status code other than 200! | ||||||
|  |  | ||||||
|  |       showStatusCodeError(context, response.statusCode); | ||||||
|  |  | ||||||
|       // TODO: Interpret the error codes and show custom message? |       // TODO: Interpret the error codes and show custom message? | ||||||
|       await showServerError(context, "Invalid response code: ${response.statusCode.toString()}"); |  | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -246,29 +238,30 @@ class InvenTreeAPI { | |||||||
|     print("Response from server: $data"); |     print("Response from server: $data"); | ||||||
|  |  | ||||||
|     // We expect certain response from the server |     // We expect certain response from the server | ||||||
|     if (!data.containsKey("server") || !data.containsKey("version")) { |     if (!data.containsKey("server") || !data.containsKey("version") || !data.containsKey("instance")) { | ||||||
|  |  | ||||||
|  |       showServerError( | ||||||
|  |         context, | ||||||
|  |         "Missing Data", | ||||||
|  |         "Server response missing required fields" | ||||||
|  |       ); | ||||||
|  |  | ||||||
|       await showServerError(context, "Server response missing required fields"); |  | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     print("Server: " + data["server"]); |     // Record server information | ||||||
|     print("Version: " + data["version"]); |  | ||||||
|  |  | ||||||
|     _version = data["version"]; |     _version = data["version"]; | ||||||
|  |  | ||||||
|     if (!_checkServerVersion(_version)) { |  | ||||||
|       await showServerError(context, "Server version is too old.\n\nServer Version: ${_version}\n\nRequired version: ${_requiredVersionString}"); |  | ||||||
|       return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Record the instance name of the server |  | ||||||
|     instance = data['instance'] ?? ''; |     instance = data['instance'] ?? ''; | ||||||
|  |  | ||||||
|     // Request token from the server if we do not already have one |     // Check that the remote server version is *new* enough | ||||||
|     if (false && _token.isNotEmpty) { |     if (!_checkServerVersion(_version)) { | ||||||
|       print("Already have token - $_token"); |       showServerError( | ||||||
|       return true; |           context, | ||||||
|  |           "Old Server Version", | ||||||
|  |           "\n\nServer Version: ${_version}\n\nRequired version: ${_requiredVersionString}" | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Clear the existing token value |     // Clear the existing token value | ||||||
| @@ -277,24 +270,33 @@ class InvenTreeAPI { | |||||||
|     print("Requesting token from server"); |     print("Requesting token from server"); | ||||||
|  |  | ||||||
|     response = await get(_URL_GET_TOKEN).timeout(Duration(seconds: 10)).catchError((error) { |     response = await get(_URL_GET_TOKEN).timeout(Duration(seconds: 10)).catchError((error) { | ||||||
|  |  | ||||||
|       print("Error requesting token:"); |       print("Error requesting token:"); | ||||||
|       print(error); |       print(error); | ||||||
|       return null; |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     if (response == null) { |     if (response == null) { | ||||||
|       await showServerError(context, "Error requesting access token"); |       showServerError( | ||||||
|  |           context, "Token Error", "Error requesting access token from server" | ||||||
|  |       ); | ||||||
|  |  | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       await showServerError(context, "Invalid status code: ${response.statusCode.toString()}"); |       showStatusCodeError(context, response.statusCode); | ||||||
|       return false; |       return false; | ||||||
|     } else { |     } else { | ||||||
|       var data = json.decode(response.body); |       var data = json.decode(response.body); | ||||||
|  |  | ||||||
|       if (!data.containsKey("token")) { |       if (!data.containsKey("token")) { | ||||||
|         await showServerError(context, "No token provided in response"); |         showServerError( | ||||||
|  |           context, | ||||||
|  |           "Missing Token", | ||||||
|  |           "Access token missing from response" | ||||||
|  |         ); | ||||||
|  |  | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -304,6 +306,7 @@ class InvenTreeAPI { | |||||||
|  |  | ||||||
|       _connected = true; |       _connected = true; | ||||||
|  |  | ||||||
|  |       // Ok, probably pretty good... | ||||||
|       return true; |       return true; | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -66,14 +66,11 @@ class BarcodeHandler { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Future<void> processBarcode(BuildContext context, QRViewController _controller, String barcode, {String url = "barcode/", bool show_dialog = false}) { |     Future<void> processBarcode(BuildContext context, QRViewController _controller, String barcode, {String url = "barcode/"}) { | ||||||
|       this._context = context; |       this._context = context; | ||||||
|       this._controller = _controller; |       this._controller = _controller; | ||||||
|  |  | ||||||
|       print("Scanned barcode data: ${barcode}"); |       print("Scanned barcode data: ${barcode}"); | ||||||
|       if (show_dialog) { |  | ||||||
|         showProgressDialog(context, "Scanning", "Sending barcode data to server"); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       // Send barcode request to server |       // Send barcode request to server | ||||||
|       InvenTreeAPI().post( |       InvenTreeAPI().post( | ||||||
| @@ -83,10 +80,6 @@ class BarcodeHandler { | |||||||
|           } |           } | ||||||
|       ).then((var response) { |       ).then((var response) { | ||||||
|  |  | ||||||
|         if (show_dialog) { |  | ||||||
|           hideProgressDialog(context); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (response.statusCode != 200) { |         if (response.statusCode != 200) { | ||||||
|           showErrorDialog( |           showErrorDialog( | ||||||
|             context, |             context, | ||||||
| @@ -119,10 +112,6 @@ class BarcodeHandler { | |||||||
|           Duration(seconds: 5) |           Duration(seconds: 5) | ||||||
|       ).catchError((error) { |       ).catchError((error) { | ||||||
|  |  | ||||||
|         if (show_dialog) { |  | ||||||
|           hideProgressDialog(context); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         showErrorDialog( |         showErrorDialog( | ||||||
|             context, |             context, | ||||||
|             "Error", |             "Error", | ||||||
|   | |||||||
| @@ -1,11 +1,11 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
|  | import 'dart:io'; | ||||||
|  |  | ||||||
| import 'package:InvenTree/api.dart'; | import 'package:InvenTree/api.dart'; | ||||||
| import 'package:InvenTree/widget/dialogs.dart'; | import 'package:InvenTree/widget/dialogs.dart'; | ||||||
| import 'package:flutter/cupertino.dart'; | import 'package:flutter/cupertino.dart'; | ||||||
| import 'package:url_launcher/url_launcher.dart'; | import 'package:url_launcher/url_launcher.dart'; | ||||||
|  |  | ||||||
| import 'package:flutter_localizations/flutter_localizations.dart'; |  | ||||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
|  |  | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| @@ -108,22 +108,21 @@ class InvenTreeModel { | |||||||
|   /* |   /* | ||||||
|    * Reload this object, by requesting data from the server |    * Reload this object, by requesting data from the server | ||||||
|    */ |    */ | ||||||
|   Future<bool> reload(BuildContext context, {bool dialog = false}) async { |   Future<bool> reload(BuildContext context) async { | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       showProgressDialog(context, I18N.of(context).refreshing, "Refreshing data for ${NAME}"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var response = await api.get(url, params: defaultGetFilters()) |     var response = await api.get(url, params: defaultGetFilters()) | ||||||
|       .timeout(Duration(seconds: 10)) |       .timeout(Duration(seconds: 10)) | ||||||
|       .catchError((e) { |       .catchError((e) { | ||||||
|  |  | ||||||
|           if (dialog) { |           if (e is SocketException) { | ||||||
|             hideProgressDialog(context); |             showServerError( | ||||||
|  |               context, | ||||||
|  |               I18N.of(context).connectionRefused, | ||||||
|  |               e.toString() | ||||||
|  |             ); | ||||||
|           } |           } | ||||||
|  |           else if (e is TimeoutException) { | ||||||
|           if (e is TimeoutException) { |             showTimeoutError(context); | ||||||
|             showTimeoutDialog(context); |  | ||||||
|           } else { |           } else { | ||||||
|             // Re-throw the error (Sentry will catch) |             // Re-throw the error (Sentry will catch) | ||||||
|             throw e; |             throw e; | ||||||
| @@ -136,17 +135,8 @@ class InvenTreeModel { | |||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       hideProgressDialog(context); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       showErrorDialog( |       showStatusCodeError(context, response.statusCode); | ||||||
|         context, |  | ||||||
|         I18N.of(context).serverError, |  | ||||||
|         "${I18N.of(context).statusCode}: ${response.statusCode}" |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       print("Error retrieving data"); |       print("Error retrieving data"); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
| @@ -159,7 +149,7 @@ class InvenTreeModel { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // POST data to update the model |   // POST data to update the model | ||||||
|   Future<bool> update(BuildContext context, {Map<String, String> values, bool show_dialog = false}) async { |   Future<bool> update(BuildContext context, {Map<String, String> values}) async { | ||||||
|  |  | ||||||
|     var addr = path.join(URL, pk.toString()); |     var addr = path.join(URL, pk.toString()); | ||||||
|  |  | ||||||
| @@ -167,20 +157,18 @@ class InvenTreeModel { | |||||||
|       addr += "/"; |       addr += "/"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (show_dialog) { |  | ||||||
|       showProgressDialog(context, "Updating ${NAME}", "Sending data to server"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var response = await api.patch(addr, body: values) |     var response = await api.patch(addr, body: values) | ||||||
|         .timeout(Duration(seconds: 10)) |         .timeout(Duration(seconds: 10)) | ||||||
|         .catchError((e) { |         .catchError((e) { | ||||||
|  |  | ||||||
|           if (show_dialog) { |           if (e is SocketException) { | ||||||
|             hideProgressDialog(context); |             showServerError( | ||||||
|           } |               context, | ||||||
|  |               I18N.of(context).connectionRefused, | ||||||
|           if (e is TimeoutException) { |               e.toString() | ||||||
|             showTimeoutDialog(context); |             ); | ||||||
|  |           } else if (e is TimeoutException) { | ||||||
|  |             showTimeoutError(context); | ||||||
|           } else { |           } else { | ||||||
|             // Re-throw the error, let Sentry report it |             // Re-throw the error, let Sentry report it | ||||||
|             throw e; |             throw e; | ||||||
| @@ -191,12 +179,8 @@ class InvenTreeModel { | |||||||
|  |  | ||||||
|     if (response == null) return false; |     if (response == null) return false; | ||||||
|  |  | ||||||
|     if (show_dialog) { |  | ||||||
|       hideProgressDialog(context); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       showErrorDialog(context, I18N.of(context).serverError, "${I18N.of(context).statusCode}: ${response.statusCode}"); |       showStatusCodeError(context, response.statusCode); | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -205,7 +189,7 @@ class InvenTreeModel { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Return the detail view for the associated pk |   // Return the detail view for the associated pk | ||||||
|   Future<InvenTreeModel> get(BuildContext context, int pk, {Map<String, String> filters, bool dialog = false}) async { |   Future<InvenTreeModel> get(BuildContext context, int pk, {Map<String, String> filters}) async { | ||||||
|  |  | ||||||
|     // TODO - Add "timeout" |     // TODO - Add "timeout" | ||||||
|     // TODO - Add error catching |     // TODO - Add error catching | ||||||
| @@ -227,20 +211,15 @@ class InvenTreeModel { | |||||||
|  |  | ||||||
|     print("GET: $addr ${params.toString()}"); |     print("GET: $addr ${params.toString()}"); | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       showProgressDialog(context, I18N.of(context).requestingData, "Requesting ${NAME} data from server"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var response = await api.get(addr, params: params) |     var response = await api.get(addr, params: params) | ||||||
|         .timeout(Duration(seconds: 10)) |         .timeout(Duration(seconds: 10)) | ||||||
|         .catchError((e) { |         .catchError((e) { | ||||||
|  |  | ||||||
|           if (dialog) { |           if (e is SocketException) { | ||||||
|             hideProgressDialog(context); |             showServerError(context, I18N.of(context).connectionRefused, e.toString()); | ||||||
|           } |           } | ||||||
|  |           else if (e is TimeoutException) { | ||||||
|           if (e is TimeoutException) { |             showTimeoutError(context); | ||||||
|             showTimeoutDialog(context); |  | ||||||
|           } else { |           } else { | ||||||
|             // Re-throw the error (handled by Sentry) |             // Re-throw the error (handled by Sentry) | ||||||
|             throw e; |             throw e; | ||||||
| @@ -252,12 +231,8 @@ class InvenTreeModel { | |||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       hideProgressDialog(context); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       showErrorDialog(context, I18N.of(context).serverError, "${I18N.of(context).statusCode}: ${response.statusCode}"); |       showStatusCodeError(context, response.statusCode); | ||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -280,15 +255,24 @@ class InvenTreeModel { | |||||||
|  |  | ||||||
|     InvenTreeModel _model; |     InvenTreeModel _model; | ||||||
|  |  | ||||||
|     await api.post(URL, body: data) |     await api.post(URL, body: data).timeout(Duration(seconds: 10)).catchError((e) { | ||||||
|     .timeout(Duration(seconds: 5)) |       print("Error during CREATE"); | ||||||
|     .catchError((e) { |  | ||||||
|       print(e.toString()); |       print(e.toString()); | ||||||
|       showErrorDialog( |  | ||||||
|         context, |       if (e is SocketException) { | ||||||
|         I18N.of(context).serverError, |         showServerError( | ||||||
|         e.toString() |             context, | ||||||
|       ); |             I18N.of(context).connectionRefused, | ||||||
|  |             e.toString() | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |       else if (e is TimeoutException) { | ||||||
|  |         showTimeoutError(context); | ||||||
|  |       } else { | ||||||
|  |         // Re-throw the error (Sentry will catch) | ||||||
|  |         throw e; | ||||||
|  |       } | ||||||
|  |  | ||||||
|       return null; |       return null; | ||||||
|     }) |     }) | ||||||
|     .then((http.Response response) { |     .then((http.Response response) { | ||||||
| @@ -297,11 +281,7 @@ class InvenTreeModel { | |||||||
|         var decoded = json.decode(response.body); |         var decoded = json.decode(response.body); | ||||||
|         _model = createFromJson(decoded); |         _model = createFromJson(decoded); | ||||||
|       } else { |       } else { | ||||||
|         showErrorDialog( |         showStatusCodeError(context, response.statusCode); | ||||||
|           context, |  | ||||||
|           I18N.of(context).serverError, |  | ||||||
|           "${I18N.of(context).statusCode}: ${response.statusCode}" |  | ||||||
|         ); |  | ||||||
|       } |       } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -309,7 +289,7 @@ class InvenTreeModel { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Return list of objects from the database, with optional filters |   // Return list of objects from the database, with optional filters | ||||||
|   Future<List<InvenTreeModel>> list(BuildContext context, {Map<String, String> filters, bool dialog=false}) async { |   Future<List<InvenTreeModel>> list(BuildContext context, {Map<String, String> filters}) async { | ||||||
|  |  | ||||||
|     if (filters == null) { |     if (filters == null) { | ||||||
|       filters = {}; |       filters = {}; | ||||||
| @@ -328,20 +308,19 @@ class InvenTreeModel { | |||||||
|     // TODO - Add "timeout" |     // TODO - Add "timeout" | ||||||
|     // TODO - Add error catching |     // TODO - Add error catching | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       showProgressDialog(context, I18N.of(context).requestingData, "Requesting ${NAME} data from server"); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     var response = await api.get(URL, params:params) |     var response = await api.get(URL, params:params) | ||||||
|       .timeout(Duration(seconds: 10)) |       .timeout(Duration(seconds: 10)) | ||||||
|       .catchError((e) { |       .catchError((e) { | ||||||
|  |  | ||||||
|         if (dialog) { |         if (e is SocketException) { | ||||||
|           hideProgressDialog(context); |           showServerError( | ||||||
|  |               context, | ||||||
|  |               I18N.of(context).connectionRefused, | ||||||
|  |               e.toString() | ||||||
|  |           ); | ||||||
|         } |         } | ||||||
|  |         else if (e is TimeoutException) { | ||||||
|         if (e is TimeoutException) { |           showTimeoutError(context); | ||||||
|           showTimeoutDialog(context); |  | ||||||
|         } else { |         } else { | ||||||
|           // Re-throw the error |           // Re-throw the error | ||||||
|           throw e; |           throw e; | ||||||
| @@ -354,15 +333,13 @@ class InvenTreeModel { | |||||||
|       return null; |       return null; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (dialog) { |  | ||||||
|       hideProgressDialog(context); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // A list of "InvenTreeModel" items |     // A list of "InvenTreeModel" items | ||||||
|     List<InvenTreeModel> results = new List<InvenTreeModel>(); |     List<InvenTreeModel> results = new List<InvenTreeModel>(); | ||||||
|  |  | ||||||
|     if (response.statusCode != 200) { |     if (response.statusCode != 200) { | ||||||
|       print("Error retreiving data"); |       showStatusCodeError(context, response.statusCode); | ||||||
|  |  | ||||||
|  |       // Return empty list | ||||||
|       return results; |       return results; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -420,8 +397,6 @@ class InvenTreeModel { | |||||||
|  |  | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -161,7 +161,6 @@ class InvenTreePart extends InvenTreeModel { | |||||||
|         "part": "${pk}", |         "part": "${pk}", | ||||||
|         "in_stock": "true", |         "in_stock": "true", | ||||||
|       }, |       }, | ||||||
|       dialog: showDialog, |  | ||||||
|     ).then((var items) { |     ).then((var items) { | ||||||
|       stockItems.clear(); |       stockItems.clear(); | ||||||
|  |  | ||||||
| @@ -186,7 +185,6 @@ class InvenTreePart extends InvenTreeModel { | |||||||
|       filters: { |       filters: { | ||||||
|         "part": "${pk}", |         "part": "${pk}", | ||||||
|       }, |       }, | ||||||
|       dialog: showDialog, |  | ||||||
|     ).then((var templates) { |     ).then((var templates) { | ||||||
|  |  | ||||||
|       testingTemplates.clear(); |       testingTemplates.clear(); | ||||||
| @@ -205,6 +203,15 @@ class InvenTreePart extends InvenTreeModel { | |||||||
|     // Get the stock count for this Part |     // Get the stock count for this Part | ||||||
|     double get inStock => double.tryParse(jsondata['in_stock'].toString() ?? '0'); |     double get inStock => double.tryParse(jsondata['in_stock'].toString() ?? '0'); | ||||||
|  |  | ||||||
|  |     String get inStockString { | ||||||
|  |  | ||||||
|  |       if (inStock == inStock.toInt()) { | ||||||
|  |         return inStock.toInt().toString(); | ||||||
|  |       } else { | ||||||
|  |         return inStock.toString(); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Get the number of units being build for this Part |     // Get the number of units being build for this Part | ||||||
|     double get building => double.tryParse(jsondata['building'].toString() ?? '0'); |     double get building => double.tryParse(jsondata['building'].toString() ?? '0'); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -97,7 +97,6 @@ class InvenTreeStockItem extends InvenTreeModel { | |||||||
|       filters: { |       filters: { | ||||||
|         "part": "${partId}", |         "part": "${partId}", | ||||||
|       }, |       }, | ||||||
|       dialog: showDialog, |  | ||||||
|     ).then((var templates) { |     ).then((var templates) { | ||||||
|       testTemplates.clear(); |       testTemplates.clear(); | ||||||
|  |  | ||||||
| @@ -113,7 +112,7 @@ class InvenTreeStockItem extends InvenTreeModel { | |||||||
|  |  | ||||||
|   int get testResultCount => testResults.length; |   int get testResultCount => testResults.length; | ||||||
|  |  | ||||||
|   Future<void> getTestResults(BuildContext context, {bool showDialog=false}) async { |   Future<void> getTestResults(BuildContext context) async { | ||||||
|  |  | ||||||
|     await InvenTreeStockItemTestResult().list( |     await InvenTreeStockItemTestResult().list( | ||||||
|       context, |       context, | ||||||
| @@ -121,7 +120,6 @@ class InvenTreeStockItem extends InvenTreeModel { | |||||||
|         "stock_item": "${pk}", |         "stock_item": "${pk}", | ||||||
|         "user_detail": "true", |         "user_detail": "true", | ||||||
|       }, |       }, | ||||||
|       dialog: showDialog, |  | ||||||
|     ).then((var results) { |     ).then((var results) { | ||||||
|       testResults.clear(); |       testResults.clear(); | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								lib/l10n
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								lib/l10n
									
									
									
									
									
								
							 Submodule lib/l10n updated: 79b2c87e96...90f3bbf1fa
									
								
							| @@ -19,6 +19,8 @@ class InvenTreeLoginSettingsWidget extends StatefulWidget { | |||||||
|  |  | ||||||
| class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | ||||||
|  |  | ||||||
|  |   final GlobalKey<_InvenTreeLoginSettingsState> _loginKey = GlobalKey<_InvenTreeLoginSettingsState>(); | ||||||
|  |  | ||||||
|   final GlobalKey<FormState> _formKey = new GlobalKey<FormState>(); |   final GlobalKey<FormState> _formKey = new GlobalKey<FormState>(); | ||||||
|  |  | ||||||
|   final GlobalKey<FormState> _addProfileKey = new GlobalKey<FormState>(); |   final GlobalKey<FormState> _addProfileKey = new GlobalKey<FormState>(); | ||||||
| @@ -176,7 +178,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | |||||||
|     _reload(); |     _reload(); | ||||||
|  |  | ||||||
|     // Attempt server login (this will load the newly selected profile |     // Attempt server login (this will load the newly selected profile | ||||||
|     InvenTreeAPI().connectToServer(context).then((result) { |     InvenTreeAPI().connectToServer(_loginKey.currentContext).then((result) { | ||||||
|       _reload(); |       _reload(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -209,7 +211,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | |||||||
|     if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) { |     if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) { | ||||||
|       // Attempt server login (this will load the newly selected profile |       // Attempt server login (this will load the newly selected profile | ||||||
|  |  | ||||||
|       InvenTreeAPI().connectToServer(context).then((result) { |       InvenTreeAPI().connectToServer(_loginKey.currentContext).then((result) { | ||||||
|         _reload(); |         _reload(); | ||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
| @@ -262,8 +264,6 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | |||||||
|  |  | ||||||
|     final Size screenSize = MediaQuery.of(context).size; |     final Size screenSize = MediaQuery.of(context).size; | ||||||
|  |  | ||||||
|     print("Building!"); |  | ||||||
|  |  | ||||||
|     List<Widget> children = []; |     List<Widget> children = []; | ||||||
|  |  | ||||||
|     if (profiles != null && profiles.length > 0) { |     if (profiles != null && profiles.length > 0) { | ||||||
| @@ -333,6 +333,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|  |       key: _loginKey, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text(I18N.of(context).profileSelect), |         title: Text(I18N.of(context).profileSelect), | ||||||
|       ), |       ), | ||||||
|   | |||||||
| @@ -268,7 +268,7 @@ class PartList extends StatelessWidget { | |||||||
|     return ListTile( |     return ListTile( | ||||||
|       title: Text("${part.name}"), |       title: Text("${part.name}"), | ||||||
|       subtitle: Text("${part.description}"), |       subtitle: Text("${part.description}"), | ||||||
|       trailing: Text("${part.inStock}"), |       trailing: Text("${part.inStockString}"), | ||||||
|       leading: InvenTreeAPI().getImage( |       leading: InvenTreeAPI().getImage( | ||||||
|         part.thumbnail, |         part.thumbnail, | ||||||
|         width: 40, |         width: 40, | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ import 'package:flutter/cupertino.dart'; | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | import 'package:font_awesome_flutter/font_awesome_flutter.dart'; | ||||||
| import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
|  | import 'package:flutter_gen/gen_l10n/app_localizations.dart'; | ||||||
|  |  | ||||||
|  |  | ||||||
| Future<void> confirmationDialog(BuildContext context, String title, String text, {String acceptText, String rejectText, Function onAccept, Function onReject}) async { | Future<void> confirmationDialog(BuildContext context, String title, String text, {String acceptText, String rejectText, Function onAccept, Function onReject}) async { | ||||||
| @@ -92,37 +93,55 @@ Future<void> showErrorDialog(BuildContext context, String title, String descript | |||||||
|  |  | ||||||
|   showDialog( |   showDialog( | ||||||
|     context: context, |     context: context, | ||||||
|     child: SimpleDialog( |     builder: (dialogContext) { | ||||||
|       title: ListTile( |       return SimpleDialog( | ||||||
|         title: Text(error), |           title: ListTile( | ||||||
|         leading: FaIcon(icon), |             title: Text(error), | ||||||
|       ), |             leading: FaIcon(icon), | ||||||
|       children: <Widget>[ |           ), | ||||||
|         ListTile( |           children: <Widget>[ | ||||||
|           title: Text(title), |             ListTile( | ||||||
|           subtitle: Text(description) |                 title: Text(title), | ||||||
|         ) |                 subtitle: Text(description) | ||||||
|       ] |             ) | ||||||
|     ) |           ] | ||||||
|   ).then((value) { |       ); | ||||||
|     if (onDismissed != null) { |     }).then((value) { | ||||||
|       onDismissed(); |       if (onDismissed != null) { | ||||||
|     } |         onDismissed(); | ||||||
|   }); |       } | ||||||
|  |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
| void showTimeoutDialog(BuildContext context) { | Future<void> showServerError(BuildContext context, String title, String description) async { | ||||||
|   /* |  | ||||||
|   Show a server timeout dialog |  | ||||||
|    */ |  | ||||||
|  |  | ||||||
|   showErrorDialog( |   if (title == null || title.isEmpty) { | ||||||
|  |     title = I18N.of(context).serverError; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   await showErrorDialog( | ||||||
|       context, |       context, | ||||||
|       I18N.of(context).timeout, |       title, | ||||||
|       I18N.of(context).noResponse |       description, | ||||||
|  |       error: I18N.of(context).serverError, | ||||||
|  |       icon: FontAwesomeIcons.server | ||||||
|   ); |   ); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | Future<void> showStatusCodeError(BuildContext context, int status, {int expected = 200}) async { | ||||||
|  |  | ||||||
|  |   await showServerError( | ||||||
|  |     context, | ||||||
|  |     "Invalid Response Code", | ||||||
|  |     "Server responded with status code ${status}" | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> showTimeoutError(BuildContext context) async { | ||||||
|  |  | ||||||
|  |   await showServerError(context, I18N.of(context).timeout, I18N.of(context).noResponse); | ||||||
|  | } | ||||||
|  |  | ||||||
| void showProgressDialog(BuildContext context, String title, String description) { | void showProgressDialog(BuildContext context, String title, String description) { | ||||||
|  |  | ||||||
|   showDialog( |   showDialog( | ||||||
|   | |||||||
| @@ -27,6 +27,8 @@ class InvenTreeHomePage extends StatefulWidget { | |||||||
|  |  | ||||||
| class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | ||||||
|  |  | ||||||
|  |   final GlobalKey<_InvenTreeHomePageState> _homeKey = GlobalKey<_InvenTreeHomePageState>(); | ||||||
|  |  | ||||||
|   _InvenTreeHomePageState() : super() { |   _InvenTreeHomePageState() : super() { | ||||||
|  |  | ||||||
|     // Initially load the profile and attempt server connection |     // Initially load the profile and attempt server connection | ||||||
| @@ -115,7 +117,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|       if (!InvenTreeAPI().isConnected() && !InvenTreeAPI().isConnecting()) { |       if (!InvenTreeAPI().isConnected() && !InvenTreeAPI().isConnecting()) { | ||||||
|  |  | ||||||
|         // Attempt server connection |         // Attempt server connection | ||||||
|         InvenTreeAPI().connectToServer(_context).then((result) { |         InvenTreeAPI().connectToServer(_homeKey.currentContext).then((result) { | ||||||
|           setState(() {}); |           setState(() {}); | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
| @@ -198,6 +200,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|     // fast, so that you can just rebuild anything that needs updating rather |     // fast, so that you can just rebuild anything that needs updating rather | ||||||
|     // than having to individually change instances of widgets. |     // than having to individually change instances of widgets. | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|  |       key: _homeKey, | ||||||
|       appBar: AppBar( |       appBar: AppBar( | ||||||
|         title: Text(I18N.of(context).appTitle), |         title: Text(I18N.of(context).appTitle), | ||||||
|         actions: <Widget>[ |         actions: <Widget>[ | ||||||
|   | |||||||
| @@ -217,7 +217,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> { | |||||||
|       ListTile( |       ListTile( | ||||||
|         title: Text(I18N.of(context).stock), |         title: Text(I18N.of(context).stock), | ||||||
|         leading: FaIcon(FontAwesomeIcons.boxes), |         leading: FaIcon(FontAwesomeIcons.boxes), | ||||||
|         trailing: Text("${part.inStock}"), |         trailing: Text("${part.inStockString}"), | ||||||
|         onTap: () { |         onTap: () { | ||||||
|           _showStock(context); |           _showStock(context); | ||||||
|         }, |         }, | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user