2
0
mirror of https://github.com/inventree/inventree-app.git synced 2025-04-28 05:26:47 +00:00

Many many changes for null-safety support

This commit is contained in:
Oliver 2021-07-09 23:56:38 +10:00
parent 2988716bf3
commit d3eec6a79e
30 changed files with 563 additions and 456 deletions

View File

@ -1,6 +1,12 @@
## InvenTree App Release Notes
---
### 0.2.6 - July 2021
---
- Major code update with "null safety" features
- Updated translations
### 0.2.5 - June 2021
---

View File

@ -22,24 +22,29 @@ import 'package:one_context/one_context.dart';
*/
class InvenTreeFileService extends FileService {
HttpClient _client;
HttpClient? _client = null;
InvenTreeFileService({HttpClient client, bool strictHttps = false}) {
InvenTreeFileService({HttpClient? client, bool strictHttps = false}) {
_client = client ?? HttpClient();
_client.badCertificateCallback = (cert, host, port) {
print("BAD CERTIFICATE CALLBACK FOR IMAGE REQUEST");
return !strictHttps;
};
if (_client != null) {
_client?.badCertificateCallback = (cert, host, port) {
print("BAD CERTIFICATE CALLBACK FOR IMAGE REQUEST");
return !strictHttps;
};
}
}
@override
Future<FileServiceResponse> get(String url,
{Map<String, String> headers = const {}}) async {
final Uri resolved = Uri.base.resolve(url);
final HttpClientRequest req = await _client.getUrl(resolved);
headers?.forEach((key, value) {
final HttpClientRequest req = await _client!.getUrl(resolved);
headers.forEach((key, value) {
req.headers.add(key, value);
});
final HttpClientResponse httpResponse = await req.close();
final http.StreamedResponse _response = http.StreamedResponse(
httpResponse.timeout(Duration(seconds: 60)), httpResponse.statusCode,
@ -101,7 +106,7 @@ class InvenTreeAPI {
String makeUrl(String endpoint) => _makeUrl(endpoint);
UserProfile profile;
UserProfile? profile = null;
Map<String, dynamic> roles = {};
@ -171,15 +176,19 @@ class InvenTreeAPI {
* - Request user token from the server
* - Request user roles from the server
*/
Future<bool> _connect(BuildContext context) async {
Future<bool> _connect() async {
if (profile == null) return false;
var ctx = OneContext().context;
String address = profile.server.trim();
String username = profile.username.trim();
String password = profile.password.trim();
String address = profile?.server ?? "";
String username = profile?.username ?? "";
String password = profile?.password ?? "";
address = address.trim();
username = username.trim();
password = password.trim();
if (address.isEmpty || username.isEmpty || password.isEmpty) {
showSnackIcon(
@ -202,7 +211,8 @@ class InvenTreeAPI {
print("Connecting to ${apiUrl} -> username=${username}");
HttpClientResponse response;
HttpClientResponse? response;
dynamic data;
response = await getResponse("");
@ -237,7 +247,7 @@ class InvenTreeAPI {
instance = data['instance'] ?? '';
// Default API version is 1 if not provided
_apiVersion = data['apiVersion'] as int ?? 1;
_apiVersion = (data['apiVersion'] ?? 1) as int;
if (_apiVersion < _minApiVersion) {
@ -315,7 +325,7 @@ class InvenTreeAPI {
}
bool disconnectFromServer() {
void disconnectFromServer() {
print("InvenTreeAPI().disconnectFromServer()");
_connected = false;
@ -324,7 +334,7 @@ class InvenTreeAPI {
profile = null;
}
Future<bool> connectToServer(BuildContext context) async {
Future<bool> connectToServer() async {
// Ensure server is first disconnected
disconnectFromServer();
@ -345,7 +355,7 @@ class InvenTreeAPI {
_connecting = true;
_connected = await _connect(context);
_connected = await _connect();
print("_connect() returned result: ${_connected}");
@ -411,12 +421,14 @@ class InvenTreeAPI {
// Perform a PATCH request
Future<dynamic> patch(String url, {Map<String, String> body, int expectedStatusCode=200}) async {
Future<dynamic> patch(String url, {Map<String, String>? body, int expectedStatusCode=200}) async {
var _url = makeApiUrl(url);
var _body = Map<String, String>();
// Copy across provided data
body.forEach((K, V) => _body[K] = V);
if (body != null) {
body.forEach((K, V) => _body[K] = V);
}
print("PATCH: " + _url);
@ -517,7 +529,8 @@ class InvenTreeAPI {
* Upload a file to the given URL
*/
Future<http.StreamedResponse> uploadFile(String url, File f,
{String name = "attachment", String method="POST", Map<String, String> fields}) async {
{String name = "attachment", String method="POST", Map<String, String>? fields}) async {
var _url = makeApiUrl(url);
var request = http.MultipartRequest(method, Uri.parse(_url));
@ -543,7 +556,8 @@ class InvenTreeAPI {
* Perform a HTTP POST request
* Returns a json object (or null if unsuccessful)
*/
Future<dynamic> post(String url, {Map<String, dynamic> body, int expectedStatusCode=201}) async {
Future<dynamic> post(String url, {Map<String, dynamic>? body, int expectedStatusCode=201}) async {
var _url = makeApiUrl(url);
print("POST: ${_url} -> ${body.toString()}");
@ -615,7 +629,7 @@ class InvenTreeAPI {
error.toString()
);
} else if (error is TimeoutException) {
showTimeoutError(ctx);
showTimeoutError();
} else {
showServerError(
L10().serverError,
@ -674,7 +688,7 @@ class InvenTreeAPI {
* and return the Response object
* (or null if the request fails)
*/
Future<HttpClientResponse> getResponse(String url, {Map<String, String> params}) async {
Future<HttpClientResponse?> getResponse(String url, {Map<String, String>? params}) async {
var _url = makeApiUrl(url);
print("GET: ${_url}");
@ -797,7 +811,7 @@ 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, int expectedStatusCode=200}) async {
Future<dynamic> get(String url, {Map<String, String>? params, int expectedStatusCode=200}) async {
var response = await getResponse(url, params: params);
@ -836,7 +850,7 @@ class InvenTreeAPI {
if (_token.isNotEmpty) {
return "Token $_token";
} else if (profile != null) {
return "Basic " + base64Encode(utf8.encode('${profile.username}:${profile.password}'));
return "Basic " + base64Encode(utf8.encode('${profile?.username}:${profile?.password}'));
} else {
return "";
}
@ -846,13 +860,11 @@ class InvenTreeAPI {
static String get staticThumb => "/static/img/blank_image.thumbnail.png";
/**
* Load image from the InvenTree server,
* or from local cache (if it has been cached!)
*/
CachedNetworkImage getImage(String imageUrl, {double height, double width}) {
CachedNetworkImage getImage(String imageUrl, {double height = 0, double width = 0}) {
if (imageUrl.isEmpty) {
imageUrl = staticImage;
}

View File

@ -35,8 +35,8 @@ class BarcodeHandler {
BarcodeHandler();
QRViewController _controller;
BuildContext _context;
QRViewController? _controller;
BuildContext? _context;
void successTone() async {
@ -58,12 +58,12 @@ class BarcodeHandler {
}
}
Future<void> onBarcodeMatched(Map<String, dynamic> data) {
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
// Called when the server "matches" a barcode
// Override this function
}
Future<void> onBarcodeUnknown(Map<String, dynamic> data) {
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
// Called when the server does not know about a barcode
// Override this function
@ -76,17 +76,17 @@ class BarcodeHandler {
);
}
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) {
Future<void> onBarcodeUnhandled(Map<String, dynamic> data) async {
failureTone();
// Called when the server returns an unhandled response
showServerError(L10().responseUnknown, data.toString());
_controller.resumeCamera();
_controller?.resumeCamera();
}
Future<void> processBarcode(BuildContext context, QRViewController _controller, String barcode, {String url = "barcode/"}) async {
Future<void> processBarcode(BuildContext? context, QRViewController? _controller, String barcode, {String url = "barcode/"}) async {
this._context = context;
this._controller = _controller;
@ -105,13 +105,13 @@ class BarcodeHandler {
}
if (data.containsKey('error')) {
_controller.resumeCamera();
_controller?.resumeCamera();
onBarcodeUnknown(data);
} else if (data.containsKey('success')) {
_controller.resumeCamera();
_controller?.resumeCamera();
onBarcodeMatched(data);
} else {
_controller.resumeCamera();
_controller?.resumeCamera();
onBarcodeUnhandled(data);
}
}
@ -128,7 +128,7 @@ class BarcodeScanHandler extends BarcodeHandler {
String getOverlayText(BuildContext context) => L10().barcodeScanGeneral;
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) {
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
failureTone();
@ -140,8 +140,9 @@ class BarcodeScanHandler extends BarcodeHandler {
}
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) {
int pk;
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
int pk = -1;
print("Handle barcode:");
print(data);
@ -149,16 +150,21 @@ class BarcodeScanHandler extends BarcodeHandler {
// A stocklocation has been passed?
if (data.containsKey('stocklocation')) {
pk = data['stocklocation']['pk'] as int ?? null;
pk = (data['stocklocation']?['pk'] ?? -1) as int;
if (pk != null) {
if (pk > 0) {
successTone();
InvenTreeStockLocation().get(_context, pk).then((var loc) {
InvenTreeStockLocation().get(pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.of(_context).pop();
Navigator.push(_context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
var _ctx = _context;
if (_ctx != null) {
Navigator.of(_ctx).pop();
Navigator.push(_ctx, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
}
});
} else {
@ -173,15 +179,24 @@ class BarcodeScanHandler extends BarcodeHandler {
} else if (data.containsKey('stockitem')) {
pk = data['stockitem']['pk'] as int ?? null;
pk = (data['stockitem']?['pk'] ?? -1) as int;
if (pk != null) {
if (pk > 0) {
successTone();
InvenTreeStockItem().get(_context, pk).then((var item) {
Navigator.of(_context).pop();
Navigator.push(_context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
InvenTreeStockItem().get(pk).then((var item) {
var _ctx = _context;
if (_ctx != null) {
// Dispose of the barcode scanner
Navigator.of(_ctx).pop();
if (item is InvenTreeStockItem) {
Navigator.push(_ctx, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
}
}
});
} else {
@ -194,15 +209,24 @@ class BarcodeScanHandler extends BarcodeHandler {
}
} else if (data.containsKey('part')) {
pk = data['part']['pk'] as int ?? null;
pk = (data['part']?['pk'] ?? -1) as int;
if (pk != null) {
if (pk > 0) {
successTone();
InvenTreePart().get(_context, pk).then((var part) {
Navigator.of(_context).pop();
Navigator.push(_context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
InvenTreePart().get(pk).then((var part) {
var _ctx = _context;
if (_ctx != null) {
// Dismiss the barcode scanner
Navigator.of(_ctx).pop();
if (part is InvenTreePart) {
Navigator.push(_ctx, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
}
}
});
} else {
@ -221,8 +245,11 @@ class BarcodeScanHandler extends BarcodeHandler {
L10().barcodeUnknown,
success: false,
onAction: () {
var _ctx = OneContext().context;
showDialog(
context: _context,
context: _ctx,
builder: (BuildContext context) => SimpleDialog(
title: Text(L10().unknownResponse),
children: <Widget>[
@ -257,7 +284,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler {
String getOverlayText(BuildContext context) => L10().barcodeScanAssign;
@override
Future<void> onBarcodeMatched(Map<String, dynamic> data) {
Future<void> onBarcodeMatched(Map<String, dynamic> data) async {
failureTone();
@ -270,7 +297,7 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler {
}
@override
Future<void> onBarcodeUnknown(Map<String, dynamic> data) {
Future<void> onBarcodeUnknown(Map<String, dynamic> data) async {
// If the barcode is unknown, we *can* assign it to the stock item!
if (!data.containsKey("hash")) {
@ -282,7 +309,6 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler {
// Send the 'hash' code as the UID for the stock item
item.update(
_context,
values: {
"uid": data['hash'],
}
@ -292,8 +318,13 @@ class StockItemBarcodeAssignmentHandler extends BarcodeHandler {
failureTone();
// Close the barcode scanner
_controller.dispose();
Navigator.of(_context).pop();
_controller?.dispose();
var _ctx = (_context);
if (_ctx != null) {
Navigator.of(_ctx).pop();
}
showSnackIcon(
L10().barcodeAssigned,
@ -350,8 +381,13 @@ class StockItemScanIntoLocationHandler extends BarcodeHandler {
successTone();
// Close the scanner
_controller.dispose();
Navigator.of(_context).pop();
_controller?.dispose();
var _ctx = _context;
if (_ctx != null) {
Navigator.of(_ctx).pop();
}
showSnackIcon(
L10().barcodeScanIntoLocationSuccess,
@ -403,7 +439,7 @@ class StockLocationScanInItemsHandler extends BarcodeHandler {
int item_id = data['stockitem']['pk'] as int;
final InvenTreeStockItem item = await InvenTreeStockItem().get(_context, item_id);
final InvenTreeStockItem? item = await InvenTreeStockItem().get(item_id) as InvenTreeStockItem;
if (item == null) {
@ -459,7 +495,7 @@ class InvenTreeQRView extends StatefulWidget {
final BarcodeHandler _handler;
InvenTreeQRView(this._handler, {Key key}) : super(key: key);
InvenTreeQRView(this._handler, {Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _QRViewState(_handler);
@ -468,11 +504,11 @@ class InvenTreeQRView extends StatefulWidget {
class _QRViewState extends State<InvenTreeQRView> {
QRViewController _controller;
QRViewController? _controller;
final BarcodeHandler _handler;
BuildContext context;
BuildContext? _context;
// In order to get hot reload to work we need to pause the camera if the platform
// is android, or resume the camera if the platform is iOS.
@ -480,9 +516,9 @@ class _QRViewState extends State<InvenTreeQRView> {
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
_controller.pauseCamera();
_controller?.pauseCamera();
} else if (Platform.isIOS) {
_controller.resumeCamera();
_controller?.resumeCamera();
}
}
@ -494,7 +530,7 @@ class _QRViewState extends State<InvenTreeQRView> {
_controller = controller;
controller.scannedDataStream.listen((barcode) {
_controller?.pauseCamera();
_handler.processBarcode(context, _controller, barcode.code);
_handler.processBarcode(_context, _controller, barcode.code);
});
}
@ -508,7 +544,7 @@ class _QRViewState extends State<InvenTreeQRView> {
Widget build(BuildContext context) {
// Save the context for later on!
this.context = context;
this._context = context;
return Scaffold(
body: Stack(

View File

@ -33,7 +33,7 @@ class InvenTreePageResponse {
int get count => _count;
int get length => results?.length ?? 0;
int get length => results.length;
List<InvenTreeModel> results = [];
}
@ -91,16 +91,16 @@ class InvenTreeModel {
}
int get pk => jsondata['pk'] ?? -1;
int get pk => (jsondata['pk'] ?? -1) as int;
// Some common accessors
String get name => jsondata['name'] ?? '';
String get description => jsondata['description'] ?? '';
String get notes => jsondata['notes'] as String ?? '';
String get notes => jsondata['notes'] ?? '';
int get parentId => jsondata['parent'] as int ?? -1;
int get parentId => (jsondata['parent'] ?? -1) as int;
// Legacy API provided external link as "URL", while newer API uses "link"
String get link => jsondata['link'] ?? jsondata['URL'] ?? '';
@ -127,7 +127,7 @@ class InvenTreeModel {
}
}
String get keywords => jsondata['keywords'] as String ?? '';
String get keywords => jsondata['keywords'] ?? '';
// Create a new object from JSON data (not a constructor!)
InvenTreeModel createFromJson(Map<String, dynamic> json) {
@ -142,7 +142,7 @@ class InvenTreeModel {
// Search this Model type in the database
Future<List<InvenTreeModel>> search(BuildContext context, String searchTerm, {Map<String, String> filters}) async {
Future<List<InvenTreeModel>> search(BuildContext context, String searchTerm, {Map<String, String>? filters}) async {
if (filters == null) {
filters = {};
@ -150,7 +150,7 @@ class InvenTreeModel {
filters["search"] = searchTerm;
final results = list(context, filters: filters);
final results = list(filters: filters);
return results;
@ -164,7 +164,7 @@ class InvenTreeModel {
/*
* Reload this object, by requesting data from the server
*/
Future<bool> reload(BuildContext context) async {
Future<bool> reload() async {
var response = await api.get(url, params: defaultGetFilters());
@ -178,7 +178,7 @@ class InvenTreeModel {
}
// POST data to update the model
Future<bool> update(BuildContext context, {Map<String, String> values}) async {
Future<bool> update({Map<String, String>? values}) async {
var addr = path.join(URL, pk.toString());
@ -198,15 +198,15 @@ class InvenTreeModel {
}
// Return the detail view for the associated pk
Future<InvenTreeModel> get(BuildContext context, int pk, {Map<String, String> filters}) async {
Future<InvenTreeModel?> get(int pk, {Map<String, String>? filters}) async {
// TODO - Add "timeout"
// TODO - Add error catching
var addr = path.join(URL, pk.toString());
var url = path.join(URL, pk.toString());
if (!addr.endsWith("/")) {
addr += "/";
if (!url.endsWith("/")) {
url += "/";
}
var params = defaultGetFilters();
@ -214,13 +214,13 @@ class InvenTreeModel {
if (filters != null) {
// Override any default values
for (String key in filters.keys) {
params[key] = filters[key];
params[key] = filters[key] ?? '';
}
}
print("GET: $addr ${params.toString()}");
print("GET: $url ${params.toString()}");
var response = await api.get(addr, params: params);
var response = await api.get(url, params: params);
if (response == null) {
return null;
@ -229,7 +229,7 @@ class InvenTreeModel {
return createFromJson(response);
}
Future<InvenTreeModel> create(BuildContext context, Map<String, dynamic> data) async {
Future<InvenTreeModel?> create(Map<String, dynamic> data) async {
print("CREATE: ${URL} ${data.toString()}");
@ -241,8 +241,6 @@ class InvenTreeModel {
data.remove('id');
}
InvenTreeModel _model;
var response = await api.post(URL, body: data);
if (response == null) {
@ -252,12 +250,12 @@ class InvenTreeModel {
return createFromJson(response);
}
Future<InvenTreePageResponse> listPaginated(int limit, int offset, {Map<String, String> filters}) async {
Future<InvenTreePageResponse?> listPaginated(int limit, int offset, {Map<String, String>? filters = null}) async {
var params = defaultListFilters();
if (filters != null) {
for (String key in filters.keys) {
params[key] = filters[key];
params[key] = filters[key] ?? '';
}
}
@ -285,14 +283,12 @@ class InvenTreeModel {
return page;
} else {
// Inavlid response
print("Invalid!");
return null;
}
}
// Return list of objects from the database, with optional filters
Future<List<InvenTreeModel>> list(BuildContext context, {Map<String, String> filters}) async {
Future<List<InvenTreeModel>> list({Map<String, String>? filters}) async {
if (filters == null) {
filters = {};
@ -302,7 +298,7 @@ class InvenTreeModel {
if (filters != null) {
for (String key in filters.keys) {
params[key] = filters[key];
params[key] = filters[key] ?? '';
}
}
@ -311,7 +307,7 @@ class InvenTreeModel {
var response = await api.get(URL, params: params);
// A list of "InvenTreeModel" items
List<InvenTreeModel> results = new List<InvenTreeModel>();
List<InvenTreeModel> results = new List<InvenTreeModel>.empty();
if (response == null) {
return results;

View File

@ -100,10 +100,11 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
}
bool passFailStatus() {
var result = latestResult();
if (result == null) {
return null;
return false;
}
return result.result;
@ -113,7 +114,7 @@ class InvenTreePartTestTemplate extends InvenTreeModel {
List<InvenTreeStockItemTestResult> results = [];
// Return the most recent test result recorded against this template
InvenTreeStockItemTestResult latestResult() {
InvenTreeStockItemTestResult? latestResult() {
if (results.isEmpty) {
return null;
}
@ -143,12 +144,12 @@ class InvenTreePart extends InvenTreeModel {
@override
Map<String, String> defaultGetFilters() {
return {
"category_detail": "1", // Include category detail information
"category_detail": "true", // Include category detail information
};
}
// Cached list of stock items
List<InvenTreeStockItem> stockItems = List<InvenTreeStockItem>();
List<InvenTreeStockItem> stockItems = List<InvenTreeStockItem>.empty();
int get stockItemCount => stockItems.length;
@ -156,7 +157,6 @@ class InvenTreePart extends InvenTreeModel {
Future<void> getStockItems(BuildContext context, {bool showDialog=false}) async {
await InvenTreeStockItem().list(
context,
filters: {
"part": "${pk}",
"in_stock": "true",
@ -172,18 +172,17 @@ class InvenTreePart extends InvenTreeModel {
});
}
int get supplier_count => jsondata['suppliers'] as int ?? 0;
int get supplier_count => (jsondata['suppliers'] ?? 0) as int;
// Cached list of test templates
List<InvenTreePartTestTemplate> testingTemplates = List<InvenTreePartTestTemplate>();
List<InvenTreePartTestTemplate> testingTemplates = List<InvenTreePartTestTemplate>.empty();
int get testTemplateCount => testingTemplates.length;
// Request test templates from the serve
Future<void> getTestTemplates(BuildContext context, {bool showDialog=false}) async {
Future<void> getTestTemplates({bool showDialog=false}) async {
InvenTreePartTestTemplate().list(
context,
filters: {
"part": "${pk}",
},
@ -200,10 +199,10 @@ class InvenTreePart extends InvenTreeModel {
}
// Get the number of stock on order for this Part
double get onOrder => double.tryParse(jsondata['ordering'].toString() ?? '0');
double get onOrder => double.tryParse(jsondata['ordering']) ?? 0;
// 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']) ?? 0;
String get inStockString {
@ -215,51 +214,55 @@ class InvenTreePart extends InvenTreeModel {
}
// 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']) ?? 0;
// Get the number of BOM items in this Part (if it is an assembly)
int get bomItemCount => jsondata['bom_items'] as int ?? 0;
int get bomItemCount => (jsondata['bom_items'] ?? 0) as int;
// Get the number of BOMs this Part is used in (if it is a component)
int get usedInCount => jsondata['used_in'] as int ?? 0;
int get usedInCount => (jsondata['used_in'] ?? 0) as int;
bool get isAssembly => jsondata['assembly'] ?? false;
bool get isAssembly => (jsondata['assembly'] ?? false) as bool;
bool get isComponent => jsondata['component'] ?? false;
bool get isComponent => (jsondata['component'] ?? false) as bool;
bool get isPurchaseable => jsondata['purchaseable'] ?? false;
bool get isPurchaseable => (jsondata['purchaseable'] ?? false) as bool;
bool get isSalable => jsondata['salable'] ?? false;
bool get isSalable => (jsondata['salable'] ?? false) as bool;
bool get isActive => jsondata['active'] ?? false;
bool get isActive => (jsondata['active'] ?? false) as bool;
bool get isVirtual => jsondata['virtual'] ?? false;
bool get isVirtual => (jsondata['virtual'] ?? false) as bool;
bool get isTrackable => jsondata['trackable'] ?? false;
bool get isTrackable => (jsondata['trackable'] ?? false) as bool;
// Get the IPN (internal part number) for the Part instance
String get IPN => jsondata['IPN'] as String ?? '';
String get IPN => jsondata['IPN'] ?? '';
// Get the revision string for the Part instance
String get revision => jsondata['revision'] as String ?? '';
String get revision => jsondata['revision'] ?? '';
// Get the category ID for the Part instance (or 'null' if does not exist)
int get categoryId => jsondata['category'] as int ?? null;
int get categoryId => (jsondata['category'] ?? -1) as int;
// Get the category name for the Part instance
String get categoryName {
if (categoryId == null) return '';
// Inavlid category ID
if (categoryId <= 0) return '';
if (!jsondata.containsKey('category_detail')) return '';
return jsondata['category_detail']['name'] as String ?? '';
return jsondata['category_detail']?['name'] ?? '';
}
// Get the category description for the Part instance
String get categoryDescription {
if (categoryId == null) return '';
// Invalid category ID
if (categoryId <= 0) return '';
if (!jsondata.containsKey('category_detail')) return '';
return jsondata['category_detail']['description'] as String ?? '';
return jsondata['category_detail']?['description'] ?? '';
}
// Get the image URL for the Part instance
String get _image => jsondata['image'] ?? '';
@ -274,7 +277,7 @@ class InvenTreePart extends InvenTreeModel {
if (fn.isNotEmpty) return fn;
List<String> elements = List<String>();
List<String> elements = List<String>.empty();
if (IPN.isNotEmpty) elements.add(IPN);
@ -324,7 +327,7 @@ class InvenTreePart extends InvenTreeModel {
}
// Return the "starred" status of this part
bool get starred => jsondata['starred'] as bool ?? false;
bool get starred => (jsondata['starred'] ?? false) as bool;
InvenTreePart() : super();

View File

@ -138,14 +138,13 @@ class InvenTreeStockItem extends InvenTreeModel {
// TODO
}
List<InvenTreePartTestTemplate> testTemplates = List<InvenTreePartTestTemplate>();
List<InvenTreePartTestTemplate> testTemplates = List<InvenTreePartTestTemplate>.empty();
int get testTemplateCount => testTemplates.length;
// Get all the test templates associated with this StockItem
Future<void> getTestTemplates(BuildContext context, {bool showDialog=false}) async {
Future<void> getTestTemplates({bool showDialog=false}) async {
await InvenTreePartTestTemplate().list(
context,
filters: {
"part": "${partId}",
},
@ -160,14 +159,13 @@ class InvenTreeStockItem extends InvenTreeModel {
});
}
List<InvenTreeStockItemTestResult> testResults = List<InvenTreeStockItemTestResult>();
List<InvenTreeStockItemTestResult> testResults = List<InvenTreeStockItemTestResult>.empty();
int get testResultCount => testResults.length;
Future<void> getTestResults(BuildContext context) async {
Future<void> getTestResults() async {
await InvenTreeStockItemTestResult().list(
context,
filters: {
"stock_item": "${pk}",
"user_detail": "true",
@ -183,7 +181,7 @@ class InvenTreeStockItem extends InvenTreeModel {
});
}
Future<bool> uploadTestResult(BuildContext context, String testName, bool result, {String value, String notes, File attachment}) async {
Future<bool> uploadTestResult(BuildContext context, String testName, bool result, {String? value, String? notes, File? attachment}) async {
Map<String, String> data = {
"stock_item": pk.toString(),
@ -204,7 +202,7 @@ class InvenTreeStockItem extends InvenTreeModel {
* TODO: Is there a nice way to refactor this one?
*/
if (attachment == null) {
var _result = await InvenTreeStockItemTestResult().create(context, data);
var _result = await InvenTreeStockItemTestResult().create(data);
return (_result != null) && (_result is InvenTreeStockItemTestResult);
} else {
@ -224,12 +222,12 @@ class InvenTreeStockItem extends InvenTreeModel {
int get partId => jsondata['part'] ?? -1;
int get trackingItemCount => jsondata['tracking_items'] as int ?? 0;
int get trackingItemCount => (jsondata['tracking_items'] ?? 0) as int;
// Date of last update
String get updated => jsondata["updated"] ?? "";
DateTime get stocktakeDate {
DateTime? get stocktakeDate {
if (jsondata.containsKey("stocktake_date")) {
if (jsondata["stocktake_date"] == null) {
return null;
@ -292,15 +290,18 @@ class InvenTreeStockItem extends InvenTreeModel {
*/
String get partThumbnail {
String thumb;
String thumb = "";
if (jsondata.containsKey('part_detail')) {
thumb = jsondata['part_detail']['thumbnail'] as String ?? '';
thumb = jsondata['part_detail']?['thumbnail'] ?? '';
// Use 'image' as a backup
if (thumb.isEmpty) {
thumb = jsondata['part_detail']?['image'] ?? '';
}
// Try a different approach
if (thumb.isEmpty) {
jsondata['part__thumbnail'] as String ?? '';
thumb = jsondata['part__thumbnail'] ?? '';
}
// Still no thumbnail? Use the 'no image' image
@ -309,7 +310,7 @@ class InvenTreeStockItem extends InvenTreeModel {
return thumb;
}
int get supplierPartId => jsondata['supplier_part'] as int ?? -1;
int get supplierPartId => (jsondata['supplier_part'] ?? -1) as int;
String get supplierImage {
String thumb = '';
@ -341,9 +342,9 @@ class InvenTreeStockItem extends InvenTreeModel {
return sku;
}
String get serialNumber => jsondata['serial'] as String ?? null;
String get serialNumber => jsondata['serial'] ?? "";
double get quantity => double.tryParse(jsondata['quantity'].toString() ?? '0');
double get quantity => double.tryParse(jsondata['quantity']) ?? 0;
String get quantityString {
@ -354,9 +355,9 @@ class InvenTreeStockItem extends InvenTreeModel {
}
}
int get locationId => jsondata['location'] as int ?? -1;
int get locationId => (jsondata['location'] ?? -1) as int;
bool isSerialized() => serialNumber != null && quantity.toInt() == 1;
bool isSerialized() => serialNumber.isNotEmpty && quantity.toInt() == 1;
String serialOrQuantityDisplay() {
if (isSerialized()) {
@ -397,7 +398,7 @@ class InvenTreeStockItem extends InvenTreeModel {
String get displayQuantity {
// Display either quantity or serial number!
if (serialNumber != null) {
if (serialNumber.isNotEmpty) {
return "SN: $serialNumber";
} else {
return quantity.toString().trim();
@ -420,7 +421,7 @@ class InvenTreeStockItem extends InvenTreeModel {
* - Remove
* - Count
*/
Future<bool> adjustStock(BuildContext context, String endpoint, double q, {String notes}) async {
Future<bool> adjustStock(BuildContext context, String endpoint, double q, {String? notes}) async {
// Serialized stock cannot be adjusted
if (isSerialized()) {
@ -456,30 +457,29 @@ class InvenTreeStockItem extends InvenTreeModel {
return true;
}
Future<bool> countStock(BuildContext context, double q, {String notes}) async {
Future<bool> countStock(BuildContext context, double q, {String? notes}) async {
final bool result = await adjustStock(context, "/stock/count", q, notes: notes);
return result;
}
Future<bool> addStock(BuildContext context, double q, {String notes}) async {
Future<bool> addStock(BuildContext context, double q, {String? notes}) async {
final bool result = await adjustStock(context, "/stock/add/", q, notes: notes);
return result;
}
Future<bool> removeStock(BuildContext context, double q, {String notes}) async {
Future<bool> removeStock(BuildContext context, double q, {String? notes}) async {
final bool result = await adjustStock(context, "/stock/remove/", q, notes: notes);
return result;
}
Future<bool> transferStock(int location, {double quantity, String notes}) async {
if (quantity == null) {} else
if ((quantity < 0) || (quantity > this.quantity)) {
Future<bool> transferStock(int location, {double? quantity, String? notes}) async {
if ((quantity == null) || (quantity < 0) || (quantity > this.quantity)) {
quantity = this.quantity;
}

View File

@ -5,5 +5,5 @@ import 'package:one_context/one_context.dart';
// Shortcut function to reduce boilerplate!
I18N L10()
{
return I18N.of(OneContext().context);
return I18N.of(OneContext().context)!;
}

@ -1 +1 @@
Subproject commit 05a5cbf63b4b5479162905def9fdadf21041212e
Subproject commit 9cc07cdb0ec0012abcec827fc776d7c5473bb75e

View File

@ -52,7 +52,7 @@ class InvenTreeApp extends StatelessWidget {
return MaterialApp(
builder: OneContext().builder,
navigatorKey: OneContext().key,
onGenerateTitle: (BuildContext context) => I18N.of(context).appTitle,
onGenerateTitle: (BuildContext context) => I18N.of(context)!.appTitle,
theme: ThemeData(
primarySwatch: Colors.lightBlue,
secondaryHeaderColor: Colors.blueGrey,

View File

@ -15,15 +15,19 @@ class InvenTreePreferencesDB {
InvenTreePreferencesDB._();
Completer<Database> _dbOpenCompleter;
Completer<Database> _dbOpenCompleter = Completer();
bool isOpen = false;
Future<Database> get database async {
// If completer is null, AppDatabaseClass is newly instantiated, so database is not yet opened
if (_dbOpenCompleter == null) {
_dbOpenCompleter = Completer();
if (!isOpen) {
// Calling _openDatabase will also complete the completer with database instance
_openDatabase();
isOpen = true;
}
// If the database is already opened, awaiting the future will happen instantly.
// Otherwise, awaiting the returned future will take some time - until complete() is called
// on the Completer in _openDatabase() below.

View File

@ -26,7 +26,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
final GlobalKey<FormState> _addProfileKey = new GlobalKey<FormState>();
List<UserProfile> profiles;
List<UserProfile> profiles = new List<UserProfile>.empty();
_InvenTreeLoginSettingsState() {
_reload();
@ -40,14 +40,14 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
});
}
void _editProfile(BuildContext context, {UserProfile userProfile, bool createNew = false}) {
void _editProfile(BuildContext context, {UserProfile? userProfile, bool createNew = false}) {
var _name;
var _server;
var _username;
var _password;
UserProfile profile;
UserProfile? profile;
if (userProfile != null) {
profile = userProfile;
@ -69,10 +69,10 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
_addProfile(profile);
} else {
profile.name = _name;
profile.server = _server;
profile.username = _username;
profile.password = _password;
profile?.name = _name;
profile?.server = _server;
profile?.username = _username;
profile?.password = _password;
_updateProfile(profile);
@ -82,28 +82,28 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
StringField(
label: L10().name,
hint: "Enter profile name",
initial: createNew ? '' : profile.name,
initial: createNew ? '' : profile?.name ?? '',
onSaved: (value) => _name = value,
validator: _validateProfileName,
),
StringField(
label: L10().server,
hint: "http[s]://<server>:<port>",
initial: createNew ? '' : profile.server,
initial: createNew ? '' : profile?.server ?? '',
validator: _validateServer,
onSaved: (value) => _server = value,
),
StringField(
label: L10().username,
hint: L10().enterPassword,
initial: createNew ? '' : profile.username,
initial: createNew ? '' : profile?.username ?? '',
onSaved: (value) => _username = value,
validator: _validateUsername,
),
StringField(
label: L10().password,
hint: L10().enterUsername,
initial: createNew ? '' : profile.password,
initial: createNew ? '' : profile?.password ?? '',
onSaved: (value) => _password = value,
validator: _validatePassword,
)
@ -111,7 +111,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
);
}
String _validateProfileName(String value) {
String? _validateProfileName(String value) {
if (value.isEmpty) {
return 'Profile name cannot be empty';
@ -122,14 +122,14 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
return null;
}
String _validateServer(String value) {
String? _validateServer(String value) {
if (value.isEmpty) {
return 'Server cannot be empty';
return L10().serverEmpty;
}
if (!value.startsWith("http:") && !value.startsWith("https:")) {
return 'Server must start with http[s]';
return L10().serverStart;
}
// TODO: URL validator
@ -137,17 +137,17 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
return null;
}
String _validateUsername(String value) {
String? _validateUsername(String value) {
if (value.isEmpty) {
return 'Username cannot be empty';
return L10().usernameEmpty;
}
return null;
}
String _validatePassword(String value) {
String? _validatePassword(String value) {
if (value.isEmpty) {
return 'Password cannot be empty';
return L10().passwordEmpty;
}
return null;
@ -158,12 +158,18 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
// Disconnect InvenTree
InvenTreeAPI().disconnectFromServer();
await UserProfileDBManager().selectProfile(profile.key);
var key = profile.key;
if (key == null) {
return;
}
await UserProfileDBManager().selectProfile(key);
_reload();
// Attempt server login (this will load the newly selected profile
InvenTreeAPI().connectToServer(_loginKey.currentContext).then((result) {
InvenTreeAPI().connectToServer().then((result) {
_reload();
});
@ -176,21 +182,25 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
_reload();
if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) {
if (InvenTreeAPI().isConnected() && profile.key == (InvenTreeAPI().profile?.key ?? '')) {
InvenTreeAPI().disconnectFromServer();
}
}
void _updateProfile(UserProfile profile) async {
void _updateProfile(UserProfile? profile) async {
if (profile == null) {
return;
}
await UserProfileDBManager().updateProfile(profile);
_reload();
if (InvenTreeAPI().isConnected() && profile.key == InvenTreeAPI().profile.key) {
if (InvenTreeAPI().isConnected() && InvenTreeAPI().profile != null && profile.key == (InvenTreeAPI().profile?.key ?? '')) {
// Attempt server login (this will load the newly selected profile
InvenTreeAPI().connectToServer(_loginKey.currentContext).then((result) {
InvenTreeAPI().connectToServer().then((result) {
_reload();
});
}
@ -203,13 +213,13 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
_reload();
}
Widget _getProfileIcon(UserProfile profile) {
Widget? _getProfileIcon(UserProfile profile) {
// Not selected? No icon for you!
if (profile == null || !profile.selected) return null;
if (!profile.selected) return null;
// Selected, but (for some reason) not the same as the API...
if (InvenTreeAPI().profile == null || InvenTreeAPI().profile.key != profile.key) {
if ((InvenTreeAPI().profile?.key ?? '') != profile.key) {
return FaIcon(
FontAwesomeIcons.questionCircle,
color: Color.fromRGBO(250, 150, 50, 1)

View File

@ -9,32 +9,32 @@ class UserProfile {
UserProfile({
this.key,
this.name,
this.server,
this.username,
this.password,
this.selected,
this.name = "",
this.server = "",
this.username = "",
this.password = "",
this.selected = false,
});
// ID of the profile
int key;
int? key;
// Name of the user profile
String name;
String name = "";
// Base address of the InvenTree server
String server;
String server = "";
// Username
String username;
String username = "";
// Password
String password;
String password = "";
bool selected = false;
// User ID (will be provided by the server on log-in)
int user_id;
int user_id = -1;
factory UserProfile.fromJson(int key, Map<String, dynamic> json, bool isSelected) => UserProfile(
key: key,
@ -122,7 +122,7 @@ class UserProfileDBManager {
print("Deleted user profile <${profile.key}> - '${profile.name}'");
}
Future<UserProfile> getSelectedProfile() async {
Future<UserProfile?> getSelectedProfile() async {
/*
* Return the currently selected profile.
*
@ -133,7 +133,7 @@ class UserProfileDBManager {
final profiles = await store.find(await _db);
List<UserProfile> profileList = new List<UserProfile>();
List<UserProfile> profileList = new List<UserProfile>.empty();
for (int idx = 0; idx < profiles.length; idx++) {
@ -158,7 +158,7 @@ class UserProfileDBManager {
final profiles = await store.find(await _db);
List<UserProfile> profileList = new List<UserProfile>();
List<UserProfile> profileList = new List<UserProfile>.empty();
for (int idx = 0; idx < profiles.length; idx++) {

View File

@ -23,9 +23,9 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class CategoryDisplayWidget extends StatefulWidget {
CategoryDisplayWidget(this.category, {Key key}) : super(key: key);
CategoryDisplayWidget(this.category, {Key? key}) : super(key: key);
final InvenTreePartCategory category;
final InvenTreePartCategory? category;
@override
_CategoryDisplayState createState() => _CategoryDisplayState(category);
@ -81,7 +81,7 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
void _editCategory(Map<String, String> values) async {
final bool result = await category.update(context, values: values);
final bool result = await category!.update(values: values);
showSnackIcon(
result ? "Category edited" : "Category editing failed",
@ -93,6 +93,11 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
void _editCategoryDialog() {
// Cannot edit top-level category
if (category == null) {
return;
}
var _name;
var _description;
@ -108,12 +113,12 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
fields: <Widget>[
StringField(
label: L10().name,
initial: category.name,
initial: category?.name,
onSaved: (value) => _name = value
),
StringField(
label: L10().description,
initial: category.description,
initial: category?.description,
onSaved: (value) => _description = value
)
]
@ -123,9 +128,9 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
_CategoryDisplayState(this.category) {}
// The local InvenTreePartCategory object
final InvenTreePartCategory category;
final InvenTreePartCategory? category;
List<InvenTreePartCategory> _subcategories = List<InvenTreePartCategory>();
List<InvenTreePartCategory> _subcategories = List<InvenTreePartCategory>.empty();
@override
Future<void> onBuild(BuildContext context) async {
@ -133,17 +138,17 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
}
@override
Future<void> request(BuildContext context) async {
Future<void> request() async {
int pk = category?.pk ?? -1;
// Update the category
if (category != null) {
await category.reload(context);
await category!.reload();
}
// Request a list of sub-categories under this one
await InvenTreePartCategory().list(context, filters: {"parent": "$pk"}).then((var cats) {
await InvenTreePartCategory().list(filters: {"parent": "$pk"}).then((var cats) {
_subcategories.clear();
for (var cat in cats) {
@ -168,10 +173,10 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
List<Widget> children = [
ListTile(
title: Text("${category.name}",
title: Text("${category?.name}",
style: TextStyle(fontWeight: FontWeight.bold)
),
subtitle: Text("${category.description}"),
subtitle: Text("${category?.description}"),
),
];
@ -179,14 +184,14 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
children.add(
ListTile(
title: Text(L10().parentCategory),
subtitle: Text("${category.parentpathstring}"),
subtitle: Text("${category?.parentpathstring}"),
leading: FaIcon(FontAwesomeIcons.levelUpAlt),
onTap: () {
if (category.parentId < 0) {
if (category == null || ((category?.parentId ?? 0) < 0)) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
} else {
// TODO - Refactor this code into the InvenTreePart class
InvenTreePartCategory().get(context, category.parentId).then((var cat) {
InvenTreePartCategory().get(category?.parentId ?? -1).then((var cat) {
if (cat is InvenTreePartCategory) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
}
@ -290,6 +295,8 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
return ListView(
children: actionTiles()
);
default:
return ListView();
}
}
}
@ -306,7 +313,7 @@ class SubcategoryList extends StatelessWidget {
void _openCategory(BuildContext context, int pk) {
// Attempt to load the sub-category.
InvenTreePartCategory().get(context, pk).then((var cat) {
InvenTreePartCategory().get(pk).then((var cat) {
if (cat is InvenTreePartCategory) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
@ -348,7 +355,7 @@ class PaginatedPartList extends StatefulWidget {
final Map<String, String> filters;
Function onTotalChanged;
Function(int)? onTotalChanged;
PaginatedPartList(this.filters, {this.onTotalChanged});
@ -363,7 +370,7 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
String _searchTerm = "";
Function onTotalChanged;
Function(int)? onTotalChanged;
final Map<String, String> filters;
@ -393,21 +400,21 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
Map<String, String> params = filters;
params["search"] = _searchTerm ?? "";
params["search"] = _searchTerm;
final bool cascade = await InvenTreeSettingsManager().getValue("partSubcategory", false);
params["cascade"] = "${cascade}";
final page = await InvenTreePart().listPaginated(_pageSize, pageKey, filters: params);
int pageLength = page.length ?? 0;
int pageCount = page.count ?? 0;
int pageLength = page?.length ?? 0;
int pageCount = page?.count ?? 0;
final isLastPage = pageLength < _pageSize;
// Construct a list of part objects
List<InvenTreePart> parts = [];
if (page == null) {
if (page != null) {
for (var result in page.results) {
if (result is InvenTreePart) {
parts.add(result);
@ -423,7 +430,7 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
}
if (onTotalChanged != null) {
onTotalChanged(pageCount);
onTotalChanged!(pageCount);
}
setState(() {
@ -438,7 +445,7 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
void _openPart(BuildContext context, int pk) {
// Attempt to load the part information
InvenTreePart().get(context, pk).then((var part) {
InvenTreePart().get(pk).then((var part) {
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));

View File

@ -13,7 +13,7 @@ class CompanyDetailWidget extends StatefulWidget {
final InvenTreeCompany company;
CompanyDetailWidget(this.company, {Key key}) : super(key: key);
CompanyDetailWidget(this.company, {Key? key}) : super(key: key);
@override
_CompanyDetailState createState() => _CompanyDetailState(company);
@ -31,8 +31,8 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
String getAppBarTitle(BuildContext context) => L10().company;
@override
Future<void> request(BuildContext context) async {
await company.reload(context);
Future<void> request() async {
await company.reload();
}
_CompanyDetailState(this.company) {
@ -42,7 +42,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
void _saveCompany(Map<String, String> values) async {
Navigator.of(context).pop();
var response = await company.update(context, values: values);
var response = await company.update(values: values);
refresh();
}
@ -66,8 +66,8 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
FlatButton(
child: Text(L10().save),
onPressed: () {
if (_editCompanyKey.currentState.validate()) {
_editCompanyKey.currentState.save();
if (_editCompanyKey.currentState!.validate()) {
_editCompanyKey.currentState!.save();
_saveCompany({
"name": _name,
@ -107,7 +107,7 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
List<Widget> _companyTiles() {
var tiles = List<Widget>();
var tiles = List<Widget>.empty();
bool sep = false;

View File

@ -11,8 +11,8 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
abstract class CompanyListWidget extends StatefulWidget {
String title;
Map<String, String> filters;
String title = "";
Map<String, String> filters = {};
@override
_CompanyListState createState() => _CompanyListState(title, filters);
@ -39,9 +39,9 @@ class CustomerListWidget extends CompanyListWidget {
class _CompanyListState extends RefreshableState<CompanyListWidget> {
var _companies = new List<InvenTreeCompany>();
var _companies = new List<InvenTreeCompany>.empty();
var _filteredCompanies = new List<InvenTreeCompany>();
var _filteredCompanies = new List<InvenTreeCompany>.empty();
String _title = "Companies";
@ -58,9 +58,9 @@ class _CompanyListState extends RefreshableState<CompanyListWidget> {
}
@override
Future<void> request(BuildContext context) async {
Future<void> request() async {
await InvenTreeCompany().list(context, filters: _filters).then((var companies) {
await InvenTreeCompany().list(filters: _filters).then((var companies) {
_companies.clear();
@ -96,8 +96,10 @@ class _CompanyListState extends RefreshableState<CompanyListWidget> {
leading: InvenTreeAPI().getImage(company.image),
onTap: () {
if (company.pk > 0) {
InvenTreeCompany().get(context, company.pk).then((var c) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c)));
InvenTreeCompany().get(company.pk).then((var c) {
if (c != null && c is InvenTreeCompany) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c)));
}
});
}
},

View File

@ -8,15 +8,10 @@ import 'package:font_awesome_flutter/font_awesome_flutter.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 {
Future<void> confirmationDialog(String title, String text, {String? acceptText, String? rejectText, Function? onAccept, Function? onReject}) async {
if (acceptText == null || acceptText.isEmpty) {
acceptText = L10().ok;
}
if (rejectText == null || rejectText.isEmpty) {
rejectText = L10().cancel;
}
String _accept = acceptText ?? L10().ok;
String _reject = rejectText ?? L10().cancel;
OneContext().showDialog(
builder: (BuildContext context) {
@ -28,7 +23,7 @@ Future<void> confirmationDialog(String title, String text, {String acceptText, S
content: Text(text),
actions: [
FlatButton(
child: Text(rejectText),
child: Text(_reject),
onPressed: () {
// Close this dialog
Navigator.pop(context);
@ -39,7 +34,7 @@ Future<void> confirmationDialog(String title, String text, {String acceptText, S
}
),
FlatButton(
child: Text(acceptText),
child: Text(_accept),
onPressed: () {
// Close this dialog
Navigator.pop(context);
@ -56,17 +51,14 @@ Future<void> confirmationDialog(String title, String text, {String acceptText, S
}
Future<void> showInfoDialog(BuildContext context, String title, String description, {IconData icon = FontAwesomeIcons.info, String info, Function onDismissed}) async {
Future<void> showInfoDialog(String title, String description, {IconData icon = FontAwesomeIcons.info, String? info, Function()? onDismissed}) async {
if (info == null || info.isEmpty) {
info = L10().info;
}
String _info = info ?? L10().info;
showDialog(
context: context,
OneContext().showDialog(
builder: (BuildContext context) => SimpleDialog(
title: ListTile(
title: Text(info),
title: Text(_info),
leading: FaIcon(icon),
),
children: <Widget>[
@ -83,16 +75,14 @@ Future<void> showInfoDialog(BuildContext context, String title, String descripti
});
}
Future<void> showErrorDialog(String title, String description, {IconData icon = FontAwesomeIcons.exclamationCircle, String error, Function onDismissed}) async {
Future<void> showErrorDialog(String title, String description, {IconData icon = FontAwesomeIcons.exclamationCircle, String? error, Function? onDismissed}) async {
if (error == null || error.isEmpty) {
error = L10().error;
}
String _error = error ?? L10().error;
OneContext().showDialog(
builder: (context) => SimpleDialog(
title: ListTile(
title: Text(error),
title: Text(_error),
leading: FaIcon(icon),
),
children: [
@ -111,7 +101,7 @@ Future<void> showErrorDialog(String title, String description, {IconData icon =
Future<void> showServerError(String title, String description) async {
if (title == null || title.isEmpty) {
if (title.isEmpty) {
title = L10().serverError;
}
@ -140,8 +130,6 @@ Future<void> showServerError(String title, String description) async {
Future<void> showStatusCodeError(int status, {int expected = 200}) async {
BuildContext ctx = OneContext().context;
String msg = L10().responseInvalid;
String extra = "Server responded with status code ${status}";
@ -174,7 +162,7 @@ Future<void> showStatusCodeError(int status, {int expected = 200}) async {
);
}
Future<void> showTimeoutError(BuildContext context) async {
Future<void> showTimeoutError() async {
// Use OneContext as "sometimes" context is null here?
var ctx = OneContext().context;
@ -182,42 +170,49 @@ Future<void> showTimeoutError(BuildContext context) async {
await showServerError(L10().timeout, L10().noResponse);
}
void showFormDialog(String title, {String acceptText, String cancelText, GlobalKey<FormState> key, List<Widget> fields, List<Widget> actions, Function callback}) {
void showFormDialog(String title, {String? acceptText, String? cancelText, GlobalKey<FormState>? key, List<Widget>? fields, List<Widget>? actions, Function? callback}) {
BuildContext dialogContext;
BuildContext? dialogContext;
var ctx = OneContext().context;
if (acceptText == null) {
acceptText = L10().save;
}
if (cancelText == null) {
cancelText = L10().cancel;
}
String _accept = acceptText ?? L10().save;
String _cancel = cancelText ?? L10().cancel;
// Undefined actions = OK + Cancel
if (actions == null) {
actions = <Widget>[
FlatButton(
child: Text(cancelText),
child: Text(_cancel),
onPressed: () {
// Close the form
Navigator.pop(dialogContext);
var _ctx = dialogContext;
if (_ctx != null) {
Navigator.pop(_ctx);
}
}
),
FlatButton(
child: Text(acceptText),
child: Text(_accept),
onPressed: () {
if (key.currentState.validate()) {
key.currentState.save();
// Close the dialog
Navigator.pop(dialogContext);
var _key = key;
// Callback
if (callback != null) {
callback();
if (_key != null && _key.currentState != null) {
if (_key.currentState!.validate()) {
_key.currentState!.save();
// Close the dialog
var _ctx = dialogContext;
if (_ctx != null) {
Navigator.pop(_ctx);
}
// Callback
if (callback != null) {
callback();
}
}
}
}
@ -225,6 +220,8 @@ void showFormDialog(String title, {String acceptText, String cancelText, GlobalK
];
}
var _fields = fields ?? List<Widget>.empty();
OneContext().showDialog(
builder: (BuildContext context) {
dialogContext = context;
@ -238,7 +235,7 @@ void showFormDialog(String title, {String acceptText, String cancelText, GlobalK
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: fields
children: _fields
)
)
)

View File

@ -52,10 +52,10 @@ class ImagePickerField extends FormField<File> {
}
ImagePickerField(BuildContext context, {String label = "Attach Image", Function onSaved, bool required = false}) :
ImagePickerField(BuildContext context, {String? label, Function(File?)? onSaved, bool required = false}) :
super(
onSaved: onSaved,
validator: (File img) {
validator: (File? img) {
if (required && (img == null)) {
return L10().required;
}
@ -63,10 +63,13 @@ class ImagePickerField extends FormField<File> {
return null;
},
builder: (FormFieldState<File> state) {
String _label = label ?? L10().attachImage;
return InputDecorator(
decoration: InputDecoration(
errorText: state.errorText,
labelText: required ? label + "*" : label,
labelText: required ? _label + "*" : _label,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
@ -92,7 +95,7 @@ class ImagePickerField extends FormField<File> {
class CheckBoxField extends FormField<bool> {
CheckBoxField({String label, String hint, bool initial = false, Function onSaved}) :
CheckBoxField({String? label, String? hint, bool initial = false, Function(bool?)? onSaved}) :
super(
onSaved: onSaved,
initialValue: initial,
@ -111,7 +114,7 @@ class CheckBoxField extends FormField<bool> {
class StringField extends TextFormField {
StringField({String label, String hint, String initial, Function onSaved, Function validator, bool allowEmpty = false, bool isEnabled = true}) :
StringField({String label = "", String? hint, String? initial, Function(String?)? onSaved, Function? validator, bool allowEmpty = false, bool isEnabled = true}) :
super(
decoration: InputDecoration(
labelText: allowEmpty ? label : label + "*",
@ -121,7 +124,7 @@ class StringField extends TextFormField {
onSaved: onSaved,
enabled: isEnabled,
validator: (value) {
if (!allowEmpty && value.isEmpty) {
if (!allowEmpty && value != null && value.isEmpty) {
return L10().valueCannotBeEmpty;
}
@ -140,7 +143,7 @@ class StringField extends TextFormField {
*/
class QuantityField extends TextFormField {
QuantityField({String label = "", String hint = "", String initial = "", double max = null, TextEditingController controller}) :
QuantityField({String label = "", String hint = "", String initial = "", double? max, TextEditingController? controller}) :
super(
decoration: InputDecoration(
labelText: label,
@ -150,9 +153,9 @@ class QuantityField extends TextFormField {
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: true),
validator: (value) {
if (value.isEmpty) return L10().quantityEmpty;
if (value != null && value.isEmpty) return L10().quantityEmpty;
double quantity = double.tryParse(value);
double quantity = double.tryParse(value ?? '0') ?? 0;
if (quantity == null) return L10().quantityInvalid;
if (quantity <= 0) return L10().quantityPositive;

View File

@ -20,7 +20,7 @@ import 'package:InvenTree/widget/spinner.dart';
import 'package:InvenTree/widget/drawer.dart';
class InvenTreeHomePage extends StatefulWidget {
InvenTreeHomePage({Key key}) : super(key: key);
InvenTreeHomePage({Key? key}) : super(key: key);
@override
_InvenTreeHomePageState createState() => _InvenTreeHomePageState();
@ -37,9 +37,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
}
// Selected user profile
UserProfile _profile;
BuildContext _context;
UserProfile? _profile;
void _searchParts() {
if (!InvenTreeAPI().checkConnection(context)) return;
@ -113,7 +111,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
if (!InvenTreeAPI().isConnected() && !InvenTreeAPI().isConnecting()) {
// Attempt server connection
InvenTreeAPI().connectToServer(_homeKey.currentContext).then((result) {
InvenTreeAPI().connectToServer().then((result) {
setState(() {});
});
}
@ -171,7 +169,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
} else {
return ListTile(
title: Text(L10().serverCouldNotConnect),
subtitle: Text("${_profile.server}"),
subtitle: Text("${_profile!.server}"),
leading: FaIcon(FontAwesomeIcons.server),
trailing: FaIcon(
FontAwesomeIcons.timesCircle,
@ -187,8 +185,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
@override
Widget build(BuildContext context) {
_context = context;
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//

View File

@ -20,11 +20,11 @@ import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
class LocationDisplayWidget extends StatefulWidget {
LocationDisplayWidget(this.location, {Key key}) : super(key: key);
LocationDisplayWidget(this.location, {Key? key}) : super(key: key);
final InvenTreeStockLocation location;
final InvenTreeStockLocation? location;
final String title = "Location";
final String title = L10().stockLocation;
@override
_LocationDisplayState createState() => _LocationDisplayState(location);
@ -32,12 +32,12 @@ class LocationDisplayWidget extends StatefulWidget {
class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final InvenTreeStockLocation location;
final InvenTreeStockLocation? location;
final _editLocationKey = GlobalKey<FormState>();
@override
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
String getAppBarTitle(BuildContext context) { return L10().stockLocation; }
@override
List<Widget> getAppBarActions(BuildContext context) {
@ -80,12 +80,16 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
void _editLocation(Map<String, String> values) async {
final bool result = await location.update(context, values: values);
bool result = false;
showSnackIcon(
result ? "Location edited" : "Location editing failed",
success: result
);
if (location != null) {
result = await location!.update(values: values);
showSnackIcon(
result ? "Location edited" : "Location editing failed",
success: result
);
}
refresh();
}
@ -95,6 +99,10 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
var _name;
var _description;
if (location == null) {
return;
}
showFormDialog(L10().editLocation,
key: _editLocationKey,
callback: () {
@ -106,12 +114,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
fields: <Widget> [
StringField(
label: L10().name,
initial: location.name,
initial: location?.name ?? '',
onSaved: (value) => _name = value,
),
StringField(
label: L10().description,
initial: location.description,
initial: location?.description ?? '',
onSaved: (value) => _description = value,
)
]
@ -120,7 +128,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
_LocationDisplayState(this.location) {}
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>.empty();
String _locationFilter = '';
@ -139,17 +147,17 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
}
@override
Future<void> request(BuildContext context) async {
Future<void> request() async {
int pk = location?.pk ?? -1;
// Reload location information
if (location != null) {
await location.reload(context);
await location?.reload();
}
// Request a list of sub-locations under this one
await InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
await InvenTreeStockLocation().list(filters: {"parent": "$pk"}).then((var locs) {
_sublocations.clear();
for (var loc in locs) {
@ -173,8 +181,8 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
List<Widget> children = [
ListTile(
title: Text("${location.name}"),
subtitle: Text("${location.description}"),
title: Text("${location!.name}"),
subtitle: Text("${location!.description}"),
),
];
@ -182,13 +190,17 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
children.add(
ListTile(
title: Text(L10().parentCategory),
subtitle: Text("${location.parentpathstring}"),
subtitle: Text("${location!.parentpathstring}"),
leading: FaIcon(FontAwesomeIcons.levelUpAlt),
onTap: () {
if (location.parentId < 0) {
int parent = location?.parentId ?? -1;
if (parent < 0) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
} else {
InvenTreeStockLocation().get(context, location.parentId).then((var loc) {
InvenTreeStockLocation().get(parent).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
@ -238,7 +250,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
Map<String, String> filters = {};
if (location != null) {
filters["location"] = "${location.pk}";
filters["location"] = "${location!.pk}";
}
switch (index) {
@ -256,7 +268,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
).toList()
);
default:
return null;
return ListView();
}
}
@ -297,7 +309,7 @@ List<Widget> detailTiles() {
List<Widget> tiles = [];
tiles.add(locationDescriptionCard(includeActions: false));
if (location != null) {
// Stock adjustment actions
if (InvenTreeAPI().checkPermission('stock', 'change')) {
@ -308,14 +320,19 @@ List<Widget> detailTiles() {
leading: FaIcon(FontAwesomeIcons.exchangeAlt),
trailing: FaIcon(FontAwesomeIcons.qrcode),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
InvenTreeQRView(
StockLocationScanInItemsHandler(location)))
).then((context) {
refresh();
});
var _loc = location;
if (_loc != null) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
InvenTreeQRView(
StockLocationScanInItemsHandler(_loc)))
).then((context) {
refresh();
});
}
},
)
);
@ -361,7 +378,7 @@ class SublocationList extends StatelessWidget {
void _openLocation(BuildContext context, int pk) {
InvenTreeStockLocation().get(context, pk).then((var loc) {
InvenTreeStockLocation().get(pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
@ -452,26 +469,32 @@ class _PaginatedStockListState extends State<PaginatedStockList> {
params["cascade"] = "${cascade}";
final page = await InvenTreeStockItem().listPaginated(_pageSize, pageKey, filters: params);
final isLastPage = page.length < _pageSize;
int pageLength = page?.length ?? 0;
int pageCount = page?.count ?? 0;
final isLastPage = pageLength < _pageSize;
// Construct a list of stock item objects
List<InvenTreeStockItem> items = [];
for (var result in page.results) {
if (result is InvenTreeStockItem) {
items.add(result);
if (page != null) {
for (var result in page.results) {
if (result is InvenTreeStockItem) {
items.add(result);
}
}
}
if (isLastPage) {
_pagingController.appendLastPage(items);
} else {
final int nextPageKey = pageKey + page.length;
final int nextPageKey = pageKey + pageLength;
_pagingController.appendPage(items, nextPageKey);
}
setState(() {
resultCount = page.count;
resultCount = pageCount;
});
} catch (error) {
@ -480,7 +503,7 @@ class _PaginatedStockListState extends State<PaginatedStockList> {
}
void _openItem(BuildContext context, int pk) {
InvenTreeStockItem().get(context, pk).then((var item) {
InvenTreeStockItem().get(pk).then((var item) {
if (item is InvenTreeStockItem) {
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
}

View File

@ -22,7 +22,7 @@ import 'location_display.dart';
class PartDetailWidget extends StatefulWidget {
PartDetailWidget(this.part, {Key key}) : super(key: key);
PartDetailWidget(this.part, {Key? key}) : super(key: key);
final InvenTreePart part;
@ -87,22 +87,22 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}
@override
Future<void> request(BuildContext context) async {
await part.reload(context);
await part.getTestTemplates(context);
Future<void> request() async {
await part.reload();
await part.getTestTemplates();
}
void _toggleStar() async {
if (InvenTreeAPI().checkPermission('part', 'view')) {
await part.update(context, values: {"starred": "${!part.starred}"});
await part.update(values: {"starred": "${!part.starred}"});
refresh();
}
}
void _savePart(Map<String, String> values) async {
final bool result = await part.update(context, values: values);
final bool result = await part.update(values: values);
if (result) {
showSnackIcon(L10().partEdited, success: true);
@ -121,7 +121,11 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
* Upload image for this Part.
* Show a SnackBar with upload result.
*/
void _uploadImage(File image) async {
void _uploadImage(File? image) async {
if (image == null) {
return;
}
final result = await part.uploadImage(image);
@ -143,7 +147,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
void _selectImage() {
File _attachment;
File? _attachment;
if (!InvenTreeAPI().checkPermission('part', 'change')) {
return;
@ -261,7 +265,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
}
// Category information
if (part.categoryName != null && part.categoryName.isNotEmpty) {
if (part.categoryName.isNotEmpty) {
tiles.add(
ListTile(
title: Text(L10().partCategory),
@ -269,9 +273,12 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
leading: FaIcon(FontAwesomeIcons.sitemap),
onTap: () {
if (part.categoryId > 0) {
InvenTreePartCategory().get(context, part.categoryId).then((var cat) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => CategoryDisplayWidget(cat)));
InvenTreePartCategory().get(part.categoryId).then((var cat) {
if (cat is InvenTreePartCategory) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => CategoryDisplayWidget(cat)));
}
});
}
},
@ -499,7 +506,7 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
)
);
default:
return null;
return Center();
}
}

View File

@ -9,7 +9,7 @@ class PartNotesWidget extends StatefulWidget {
final InvenTreePart part;
PartNotesWidget(this.part, {Key key}) : super(key: key);
PartNotesWidget(this.part, {Key? key}) : super(key: key);
@override
_PartNotesState createState() => _PartNotesState(part);

View File

@ -11,7 +11,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
final refreshableKey = GlobalKey<ScaffoldState>();
// Storage for context once "Build" is called
BuildContext context;
BuildContext? _context;
// Current tab index (used for widgets which display bottom tabs)
int tabIndex = 0;
@ -36,7 +36,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => onBuild(context));
WidgetsBinding.instance?.addPostFrameCallback((_) => onBuild(_context!));
}
// Function called after the widget is first build
@ -45,7 +45,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
}
// Function to request data for this page
Future<void> request(BuildContext context) async {
Future<void> request() async {
return;
}
@ -55,7 +55,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
loading = true;
});
await request(context);
await request();
setState(() {
loading = false;
@ -77,14 +77,16 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
// Function to construct a body (MUST BE PROVIDED)
Widget getBody(BuildContext context) {
// Default return is an empty ListView
return ListView();
}
Widget? getBottomNavBar(BuildContext context) {
return null;
}
Widget getBottomNavBar(BuildContext context) {
return null;
}
Widget getFab(BuildContext context) {
Widget? getFab(BuildContext context) {
return null;
}
@ -92,7 +94,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
Widget build(BuildContext context) {
// Save the context for future use
this.context = context;
_context = context;
return Scaffold(
key: refreshableKey,

View File

@ -15,25 +15,21 @@ import '../api.dart';
// TODO - Refactor duplicate code in this file!
class PartSearchDelegate extends SearchDelegate<InvenTreePart> {
class PartSearchDelegate extends SearchDelegate<InvenTreePart?> {
final partSearchKey = GlobalKey<ScaffoldState>();
BuildContext context;
// What did we search for last time?
String _cachedQuery;
String _cachedQuery = "";
bool _searching = false;
// Custom filters for the part search
Map<String, String> filters = {};
PartSearchDelegate(this.context, {this.filters}) {
if (filters == null) {
filters = {};
}
}
PartSearchDelegate(this.context, {this.filters = const {}});
@override
String get searchFieldLabel => L10().searchParts;
@ -71,7 +67,7 @@ class PartSearchDelegate extends SearchDelegate<InvenTreePart> {
for (int idx = 0; idx < results.length; idx++) {
if (results[idx] is InvenTreePart) {
partResults.add(results[idx]);
partResults.add(results[idx] as InvenTreePart);
}
}
@ -132,7 +128,7 @@ class PartSearchDelegate extends SearchDelegate<InvenTreePart> {
),
trailing: Text(part.inStockString),
onTap: () {
InvenTreePart().get(context, part.pk).then((var prt) {
InvenTreePart().get(part.pk).then((var prt) {
if (prt is InvenTreePart) {
Navigator.push(
context,
@ -201,18 +197,18 @@ class PartSearchDelegate extends SearchDelegate<InvenTreePart> {
}
class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem> {
class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem?> {
final stockSearchKey = GlobalKey<ScaffoldState>();
final BuildContext context;
String _cachedQuery;
String _cachedQuery = "";
bool _searching = false;
// Custom filters for the stock item search
Map<String, String> filters;
Map<String, String>? filters;
StockSearchDelegate(this.context, {this.filters}) {
if (filters == null) {
@ -247,7 +243,9 @@ class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem> {
showResults(context);
// Enable cascading part search by default
filters["cascade"] = "true";
if (filters != null) {
filters?["cascade"] = "true";
}
final results = await InvenTreeStockItem().search(
context, query, filters: filters);
@ -256,7 +254,7 @@ class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem> {
for (int idx = 0; idx < results.length; idx++) {
if (results[idx] is InvenTreeStockItem) {
itemResults.add(results[idx]);
itemResults.add(results[idx] as InvenTreeStockItem);
}
}
@ -315,7 +313,7 @@ class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem> {
),
trailing: Text(item.serialOrQuantityDisplay()),
onTap: () {
InvenTreeStockItem().get(context, item.pk).then((var it) {
InvenTreeStockItem().get(item.pk).then((var it) {
if (it is InvenTreeStockItem) {
Navigator.push(
context,

View File

@ -15,14 +15,14 @@ import 'package:one_context/one_context.dart';
import 'package:InvenTree/l10.dart';
void showSnackIcon(String text, {IconData icon, Function onAction, bool success, String actionText}) {
void showSnackIcon(String text, {IconData? icon, Function()? onAction, bool? success, String? actionText}) {
OneContext().hideCurrentSnackBar();
Color backgroundColor;
Color backgroundColor = Colors.deepOrange;
// Make some selections based on the "success" value
if (success == true) {
if (success != null && success == true) {
backgroundColor = Colors.lightGreen;
// Select an icon if we do not have an action
@ -30,26 +30,21 @@ void showSnackIcon(String text, {IconData icon, Function onAction, bool success,
icon = FontAwesomeIcons.checkCircle;
}
} else if (success == false) {
} else if (success != null && success == false) {
backgroundColor = Colors.deepOrange;
if (icon == null && onAction == null) {
icon = FontAwesomeIcons.exclamationCircle;
}
}
SnackBarAction action;
String _action = actionText ?? L10().details;
SnackBarAction? action;
if (onAction != null) {
if (actionText == null) {
// Default action text
actionText = L10().details;
}
action = SnackBarAction(
label: actionText,
label: _action,
onPressed: onAction,
);
}

View File

@ -4,13 +4,13 @@ import 'package:flutter/cupertino.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class Spinner extends StatefulWidget {
final IconData icon;
final IconData? icon;
final Duration duration;
final Color color;
const Spinner({
this.color = const Color.fromRGBO(150, 150, 150, 1),
Key key,
Key? key,
@required this.icon,
this.duration = const Duration(milliseconds: 1800),
}) : super(key: key);
@ -20,8 +20,8 @@ class Spinner extends StatefulWidget {
}
class _SpinnerState extends State<Spinner> with SingleTickerProviderStateMixin {
AnimationController _controller;
Widget _child;
AnimationController? _controller;
Widget? _child;
@override
void initState() {
@ -40,14 +40,14 @@ class _SpinnerState extends State<Spinner> with SingleTickerProviderStateMixin {
@override
void dispose() {
_controller.dispose();
_controller!.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return RotationTransition(
turns: _controller,
turns: _controller!,
child: _child,
);
}

View File

@ -14,7 +14,7 @@ import '../api.dart';
class StarredPartWidget extends StatefulWidget {
StarredPartWidget({Key key}) : super(key: key);
StarredPartWidget({Key? key}) : super(key: key);
@override
_StarredPartState createState() => _StarredPartState();
@ -29,15 +29,17 @@ class _StarredPartState extends RefreshableState<StarredPartWidget> {
String getAppBarTitle(BuildContext context) => L10().partsStarred;
@override
Future<void> request(BuildContext context) async {
Future<void> request() async {
final parts = await InvenTreePart().list(context, filters: {"starred": "true"});
final parts = await InvenTreePart().list(filters: {"starred": "true"});
starredParts.clear();
for (int idx = 0; idx < parts.length; idx++) {
if (parts[idx] is InvenTreePart) {
starredParts.add(parts[idx]);
if (parts != null) {
for (int idx = 0; idx < parts.length; idx++) {
if (parts[idx] is InvenTreePart) {
starredParts.add(parts[idx] as InvenTreePart);
}
}
}
}
@ -54,7 +56,7 @@ class _StarredPartState extends RefreshableState<StarredPartWidget> {
height: 40
),
onTap: () {
InvenTreePart().get(context, part.pk).then((var prt) {
InvenTreePart().get(part.pk).then((var prt) {
if (prt is InvenTreePart) {
Navigator.push(
context,

View File

@ -25,7 +25,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class StockDetailWidget extends StatefulWidget {
StockDetailWidget(this.item, {Key key}) : super(key: key);
StockDetailWidget(this.item, {Key? key}) : super(key: key);
final InvenTreeStockItem item;
@ -77,7 +77,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
final InvenTreeStockItem item;
// Part object
InvenTreePart part;
InvenTreePart? part;
@override
Future<void> onBuild(BuildContext context) async {
@ -89,14 +89,14 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
}
@override
Future<void> request(BuildContext context) async {
await item.reload(context);
Future<void> request() async {
await item.reload();
// Request part information
part = await InvenTreePart().get(context, item.partId);
part = await InvenTreePart().get(item.partId) as InvenTreePart;
// Request test results...
await item.getTestResults(context);
await item.getTestResults();
}
void _addStock() async {
@ -227,7 +227,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
void _unassignBarcode(BuildContext context) async {
final bool result = await item.update(context, values: {'uid': ''});
final bool result = await item.update(values: {'uid': ''});
if (result) {
showSnackIcon(
@ -245,7 +245,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
}
void _transferStock(BuildContext context, InvenTreeStockLocation location) async {
void _transferStock(InvenTreeStockLocation location) async {
double quantity = double.tryParse(_quantityController.text) ?? item.quantity;
String notes = _notesController.text;
@ -264,17 +264,21 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
void _transferStockDialog() async {
var locations = await InvenTreeStockLocation().list(context);
var locations = await InvenTreeStockLocation().list();
final _selectedController = TextEditingController();
InvenTreeStockLocation selectedLocation;
InvenTreeStockLocation? selectedLocation;
_quantityController.text = "${item.quantityString}";
showFormDialog(L10().transferStock,
key: _moveStockKey,
callback: () {
_transferStock(context, selectedLocation);
var _loc = selectedLocation;
if (_loc != null) {
_transferStock(_loc);
}
},
fields: <Widget>[
QuantityField(
@ -292,7 +296,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
)
),
suggestionsCallback: (pattern) async {
var suggestions = List<InvenTreeStockLocation>();
var suggestions = List<InvenTreeStockLocation>.empty();
for (var loc in locations) {
if (loc.matchAgainstString(pattern)) {
@ -311,7 +315,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
},
onSuggestionSelected: (suggestion) {
selectedLocation = suggestion as InvenTreeStockLocation;
_selectedController.text = selectedLocation.pathstring;
_selectedController.text = selectedLocation!.pathstring;
},
onSaved: (value) {
},
@ -342,7 +346,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
),
onTap: () {
if (item.partId > 0) {
InvenTreePart().get(context, item.partId).then((var part) {
InvenTreePart().get(item.partId).then((var part) {
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
}
@ -397,9 +401,12 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
onTap: () {
if (item.locationId > 0) {
InvenTreeStockLocation().get(context, item.locationId).then((var loc) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => LocationDisplayWidget(loc)));
InvenTreeStockLocation().get(item.locationId).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => LocationDisplayWidget(loc)));
}
});
}
},
@ -442,7 +449,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
);
}
if ((item.testResultCount > 0) || (part != null && part.isTrackable)) {
if ((item.testResultCount > 0) || (part?.isTrackable ?? false)) {
tiles.add(
ListTile(
title: Text(L10().testResults),
@ -641,7 +648,7 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
).toList()
);
default:
return null;
return ListView();
}
}

View File

@ -19,7 +19,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
class StockItemTestResultsWidget extends StatefulWidget {
StockItemTestResultsWidget(this.item, {Key key}) : super(key: key);
StockItemTestResultsWidget(this.item, {Key? key}) : super(key: key);
final InvenTreeStockItem item;
@ -36,16 +36,16 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
String getAppBarTitle(BuildContext context) => L10().testResults;
@override
Future<void> request(BuildContext context) async {
await item.getTestTemplates(context);
await item.getTestResults(context);
Future<void> request() async {
await item.getTestTemplates();
await item.getTestResults();
}
final InvenTreeStockItem item;
_StockItemTestResultDisplayState(this.item);
void uploadTestResult(String name, bool result, String value, String notes, File attachment) async {
void uploadTestResult(String name, bool result, String value, String notes, File? attachment) async {
final success = await item.uploadTestResult(
context, name, result,
@ -64,11 +64,11 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
void addTestResult({String name = '', bool nameIsEditable = true, bool result = false, String value = '', bool valueRequired = false, bool attachmentRequired = false}) async {
String _name;
bool _result;
String _value;
String _notes;
File _attachment;
String _name = "";
bool _result = false;
String _value = "";
String _notes = "";
File? _attachment;
showFormDialog(L10().testResultAdd,
key: _addResultKey,
@ -80,21 +80,21 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
label: L10().testName,
initial: name,
isEnabled: nameIsEditable,
onSaved: (value) => _name = value,
onSaved: (value) => _name = value ?? '',
),
CheckBoxField(
label: L10().result,
hint: L10().testPassedOrFailed,
initial: true,
onSaved: (value) => _result = value,
onSaved: (value) => _result = value ?? false,
),
StringField(
label: L10().value,
initial: value,
allowEmpty: true,
onSaved: (value) => _value = value,
onSaved: (value) => _value = value ?? '',
validator: (String value) {
if (valueRequired && (value == null || value.isEmpty)) {
if (valueRequired && value.isEmpty) {
return L10().valueRequired;
}
return null;
@ -109,7 +109,7 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
StringField(
allowEmpty: true,
label: L10().notes,
onSaved: (value) => _notes = value,
onSaved: (value) => _notes = value ?? '',
),
]
);
@ -202,10 +202,11 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
for (var item in results) {
bool _required = false;
String _test;
bool _result = null;
String _value;
String _notes;
String _test = "";
bool _result = false;
String _value = "";
String _notes = "";
FaIcon _icon = FaIcon(FontAwesomeIcons.questionCircle, color: Color.fromRGBO(0, 0, 250, 1));
bool _valueRequired = false;
bool _attachmentRequired = false;

View File

@ -10,7 +10,7 @@ class StockNotesWidget extends StatefulWidget {
final InvenTreeStockItem item;
StockNotesWidget(this.item, {Key key}) : super(key: key);
StockNotesWidget(this.item, {Key? key}) : super(key: key);
@override
_StockNotesState createState() => _StockNotesState(item);

View File

@ -23,23 +23,23 @@ dependencies:
cupertino_icons: ^0.1.3
http: ^0.13.0
cached_network_image: ^2.5.0
cached_network_image: ^2.5.0 # Download and cache remote images
qr_code_scanner: ^0.3.5 # Barcode scanning
package_info: ^2.0.0 # App information introspection
device_info: ^2.0.0 # Information about the device
font_awesome_flutter: ^8.8.1 # FontAwesome icon set
flutter_speed_dial: ^1.2.5 # FAB menu elements
sentry_flutter: 5.0.0 # Error reporting
sentry_flutter: 5.0.0 # Error reporting
flutter_typeahead: ^1.8.1 # Auto-complete input field
image_picker: ^0.8.0 # Select or take photos
url_launcher: 6.0.0 # Open link in system browser
url_launcher: 6.0.0 # Open link in system browser
flutter_markdown: ^0.6.2 # Rendering markdown
camera: # Camera
path_provider: 2.0.1 #^1.6.28 # Local file storage
path_provider: 2.0.1 # Local file storage
sembast: ^2.4.9 # NoSQL data storage
one_context: ^0.5.0 # Dialogs without requiring context
infinite_scroll_pagination: ^2.3.0 # Let the server do all the work!
audioplayers: ^0.19.0
audioplayers: ^0.19.0 # Play audio files
path:
dev_dependencies: