mirror of
https://github.com/inventree/inventree-app.git
synced 2025-05-01 15:06:49 +00:00
Merge branch 'null-safety'
This commit is contained in:
commit
a52ae09b2b
@ -22,8 +22,8 @@ if (flutterVersionName == null) {
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
apply plugin: 'kotlin-android'
|
||||
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||
|
||||
def keystoreProperties = new Properties()
|
||||
def keystorePropertiesFile = rootProject.file('key.properties')
|
||||
@ -32,7 +32,11 @@ if (keystorePropertiesFile.exists()) {
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 29
|
||||
compileSdkVersion 30
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||
@ -45,7 +49,7 @@ android {
|
||||
defaultConfig {
|
||||
applicationId "inventree.inventree_app"
|
||||
minSdkVersion 25
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
@ -78,7 +82,8 @@ dependencies {
|
||||
testImplementation 'junit:junit:4.12'
|
||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||
androidTestImplementation 'com.android.support:multidex:1.0.0'
|
||||
androidTestImplementation 'com.android.support:multidex:2.0.1'
|
||||
implementation "androidx.core:core:1.5.0-rc01"
|
||||
implementation 'androidx.appcompat:appcompat:1.0.0'
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
buildscript {
|
||||
|
||||
ext.kotlin_version = '1.4.21'
|
||||
ext.kotlin_version = '1.5.10'
|
||||
|
||||
repositories {
|
||||
google()
|
||||
@ -8,7 +8,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
||||
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
@ -25,12 +25,7 @@ subprojects {
|
||||
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||
project.evaluationDependsOn(':app')
|
||||
project.configurations.all {
|
||||
resolutionStrategy.eachDependency { details ->
|
||||
if (details.requested.group == 'androidx.core' &&
|
||||
!details.requested.name.contains('androidx')) {
|
||||
details.useVersion "1.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
|
@ -1,6 +1,14 @@
|
||||
## InvenTree App Release Notes
|
||||
---
|
||||
|
||||
### 0.2.6 - July 2021
|
||||
---
|
||||
|
||||
- Major code update with "null safety" features
|
||||
- Handle case of improperly formatted hostname
|
||||
- Multiple API bug fixes (mostly null references)
|
||||
- Updated translations
|
||||
|
||||
### 0.2.5 - June 2021
|
||||
---
|
||||
|
||||
|
@ -1,13 +1,13 @@
|
||||
#!/bin/sh
|
||||
# This is a generated file; do not edit or check into version control.
|
||||
export "FLUTTER_ROOT=C:\flutter"
|
||||
export "FLUTTER_ROOT=c:\flutter"
|
||||
export "FLUTTER_APPLICATION_PATH=C:\inventree-app"
|
||||
export "COCOAPODS_PARALLEL_CODE_SIGN=true"
|
||||
export "FLUTTER_TARGET=lib\main.dart"
|
||||
export "FLUTTER_BUILD_DIR=build"
|
||||
export "SYMROOT=${SOURCE_ROOT}/../build\ios"
|
||||
export "FLUTTER_BUILD_NAME=0.1.5"
|
||||
export "FLUTTER_BUILD_NUMBER=9"
|
||||
export "FLUTTER_BUILD_NAME=0.2.6"
|
||||
export "FLUTTER_BUILD_NUMBER=14"
|
||||
export "DART_OBFUSCATION=false"
|
||||
export "TRACK_WIDGET_CREATION=false"
|
||||
export "TREE_SHAKE_ICONS=false"
|
||||
|
229
lib/api.dart
229
lib/api.dart
@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:InvenTree/inventree/sentry.dart';
|
||||
import 'package:InvenTree/user_profile.dart';
|
||||
import 'package:InvenTree/widget/snacks.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:InvenTree/widget/dialogs.dart';
|
||||
import 'package:InvenTree/l10.dart';
|
||||
import 'package:InvenTree/inventree/sentry.dart';
|
||||
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:one_context/one_context.dart';
|
||||
@ -22,24 +24,31 @@ 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) {
|
||||
|
||||
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 {
|
||||
{Map<String, String>? headers}) 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);
|
||||
|
||||
if (headers != null) {
|
||||
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 +110,7 @@ class InvenTreeAPI {
|
||||
|
||||
String makeUrl(String endpoint) => _makeUrl(endpoint);
|
||||
|
||||
UserProfile profile;
|
||||
UserProfile? profile = null;
|
||||
|
||||
Map<String, dynamic> roles = {};
|
||||
|
||||
@ -171,15 +180,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 +215,8 @@ class InvenTreeAPI {
|
||||
|
||||
print("Connecting to ${apiUrl} -> username=${username}");
|
||||
|
||||
HttpClientResponse response;
|
||||
HttpClientResponse? response;
|
||||
|
||||
dynamic data;
|
||||
|
||||
response = await getResponse("");
|
||||
@ -237,7 +251,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 +329,7 @@ class InvenTreeAPI {
|
||||
|
||||
}
|
||||
|
||||
bool disconnectFromServer() {
|
||||
void disconnectFromServer() {
|
||||
print("InvenTreeAPI().disconnectFromServer()");
|
||||
|
||||
_connected = false;
|
||||
@ -324,7 +338,7 @@ class InvenTreeAPI {
|
||||
profile = null;
|
||||
}
|
||||
|
||||
Future<bool> connectToServer(BuildContext context) async {
|
||||
Future<bool> connectToServer() async {
|
||||
|
||||
// Ensure server is first disconnected
|
||||
disconnectFromServer();
|
||||
@ -345,7 +359,7 @@ class InvenTreeAPI {
|
||||
|
||||
_connecting = true;
|
||||
|
||||
_connected = await _connect(context);
|
||||
_connected = await _connect();
|
||||
|
||||
print("_connect() returned result: ${_connected}");
|
||||
|
||||
@ -411,7 +425,7 @@ 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 = const {}, int expectedStatusCode=200}) async {
|
||||
var _url = makeApiUrl(url);
|
||||
var _body = Map<String, String>();
|
||||
|
||||
@ -433,7 +447,7 @@ class InvenTreeAPI {
|
||||
// Open a connection to the server
|
||||
HttpClientRequest request = await client.patchUrl(uri)
|
||||
.timeout(Duration(seconds: 10))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("PATCH request return error");
|
||||
print("URL: ${uri}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -446,12 +460,14 @@ class InvenTreeAPI {
|
||||
error.toString(),
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -462,7 +478,7 @@ class InvenTreeAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = json.encode(body);
|
||||
var data = json.encode(_body);
|
||||
|
||||
// Set headers
|
||||
request.headers.set('Accept', 'application/json');
|
||||
@ -474,7 +490,7 @@ class InvenTreeAPI {
|
||||
|
||||
HttpClientResponse response = await request.close()
|
||||
.timeout(Duration(seconds: 30))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("PATCH request returned error");
|
||||
print("URL: ${_url}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -487,28 +503,43 @@ class InvenTreeAPI {
|
||||
error.toString()
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (response == null) {
|
||||
print("null response from PATCH ${_url}");
|
||||
return null;
|
||||
}
|
||||
var responseData = await responseToJson(response);
|
||||
|
||||
if (response.statusCode != expectedStatusCode) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
return null;
|
||||
|
||||
print("PATCH to ${_url} returned status code ${response.statusCode}");
|
||||
print("Data:");
|
||||
print(responseData);
|
||||
|
||||
// Server error
|
||||
if (response.statusCode >= 500) {
|
||||
sentryReportMessage(
|
||||
"Server error on PATCH request",
|
||||
context: {
|
||||
"url": _url,
|
||||
"statusCode": "${response.statusCode}",
|
||||
"response": responseData.toString(),
|
||||
"request": body.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var responseData = await responseToJson(response);
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
@ -517,7 +548,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));
|
||||
@ -536,6 +568,21 @@ class InvenTreeAPI {
|
||||
|
||||
var response = await request.send();
|
||||
|
||||
if (response.statusCode >= 500) {
|
||||
// Server error
|
||||
if (response.statusCode >= 500) {
|
||||
sentryReportMessage(
|
||||
"Server error on file upload",
|
||||
context: {
|
||||
"url": _url,
|
||||
"statusCode": "${response.statusCode}",
|
||||
"response": response.toString(),
|
||||
"request": request.fields.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@ -543,7 +590,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 = const {}, int expectedStatusCode=201}) async {
|
||||
|
||||
var _url = makeApiUrl(url);
|
||||
|
||||
print("POST: ${_url} -> ${body.toString()}");
|
||||
@ -560,7 +608,7 @@ class InvenTreeAPI {
|
||||
// Open a connection to the server
|
||||
HttpClientRequest request = await client.postUrl(uri)
|
||||
.timeout(Duration(seconds: 10))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("POST request returned error");
|
||||
print("URL: ${uri}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -573,21 +621,19 @@ class InvenTreeAPI {
|
||||
error.toString()
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = json.encode(body);
|
||||
|
||||
// Set headers
|
||||
@ -602,7 +648,7 @@ class InvenTreeAPI {
|
||||
|
||||
HttpClientResponse response = await request.close()
|
||||
.timeout(Duration(seconds: 30))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("POST request returned error");
|
||||
print("URL: ${_url}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -615,12 +661,14 @@ class InvenTreeAPI {
|
||||
error.toString()
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -631,12 +679,30 @@ class InvenTreeAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseData = await responseToJson(response);
|
||||
|
||||
if (response.statusCode != expectedStatusCode) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
return null;
|
||||
|
||||
print("POST to ${_url} returned status code ${response.statusCode}");
|
||||
print("Data:");
|
||||
print(responseData);
|
||||
|
||||
// Server error
|
||||
if (response.statusCode >= 500) {
|
||||
sentryReportMessage(
|
||||
"Server error on POST request",
|
||||
context: {
|
||||
"url": _url,
|
||||
"statusCode": "${response.statusCode}",
|
||||
"response": responseData.toString(),
|
||||
"request": body.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
var responseData = await responseToJson(response);
|
||||
return null;
|
||||
}
|
||||
|
||||
return responseData;
|
||||
}
|
||||
@ -674,13 +740,13 @@ 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 = const {}}) async {
|
||||
var _url = makeApiUrl(url);
|
||||
|
||||
print("GET: ${_url}");
|
||||
|
||||
// If query parameters are supplied, form a query string
|
||||
if (params != null && params.isNotEmpty) {
|
||||
if (params.isNotEmpty) {
|
||||
String query = '?';
|
||||
|
||||
params.forEach((K, V) => query += K + '=' + V + '&');
|
||||
@ -695,7 +761,12 @@ class InvenTreeAPI {
|
||||
|
||||
var client = createClient(true);
|
||||
|
||||
final uri = Uri.parse(_url);
|
||||
Uri? uri = Uri.tryParse(_url);
|
||||
|
||||
if (uri == null) {
|
||||
showServerError(L10().invalidHost, L10().invalidHostDetails);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check for invalid host
|
||||
if (uri.host.isEmpty) {
|
||||
@ -703,10 +774,13 @@ class InvenTreeAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
HttpClientRequest? request;
|
||||
|
||||
try {
|
||||
// Open a connection
|
||||
HttpClientRequest request = await client.getUrl(uri)
|
||||
request = await client.getUrl(uri)
|
||||
.timeout(Duration(seconds: 10))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("GET request returned error");
|
||||
print("URL: ${uri}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -719,16 +793,41 @@ class InvenTreeAPI {
|
||||
error.toString()
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
if (error is FormatException) {
|
||||
showServerError(
|
||||
L10().invalidHost,
|
||||
L10().invalidHostDetails)
|
||||
;
|
||||
} else if (error is SocketException) {
|
||||
showServerError(
|
||||
L10().connectionRefused,
|
||||
error.toString()
|
||||
);
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
// Report to sentry
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (request == null) {
|
||||
return null;
|
||||
@ -740,7 +839,7 @@ class InvenTreeAPI {
|
||||
|
||||
HttpClientResponse response = await request.close()
|
||||
.timeout(Duration(seconds: 10))
|
||||
.catchError((error) {
|
||||
.catchError((error, stackTrace) {
|
||||
print("GET request returned error");
|
||||
print("URL: ${_url}");
|
||||
print("Error: ${error.toString()}");
|
||||
@ -753,12 +852,14 @@ class InvenTreeAPI {
|
||||
error.toString()
|
||||
);
|
||||
} else if (error is TimeoutException) {
|
||||
showTimeoutError(ctx);
|
||||
showTimeoutError();
|
||||
} else {
|
||||
showServerError(
|
||||
L10().serverError,
|
||||
error.toString()
|
||||
);
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -769,10 +870,6 @@ class InvenTreeAPI {
|
||||
|
||||
dynamic responseToJson(HttpClientResponse response) async {
|
||||
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String body = await response.transform(utf8.decoder).join();
|
||||
|
||||
try {
|
||||
@ -797,7 +894,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 = const {}, int expectedStatusCode=200}) async {
|
||||
|
||||
var response = await getResponse(url, params: params);
|
||||
|
||||
@ -807,15 +904,29 @@ class InvenTreeAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
var responseData = await responseToJson(response);
|
||||
|
||||
// Check the status code of the response
|
||||
if (response.statusCode != expectedStatusCode) {
|
||||
showStatusCodeError(response.statusCode);
|
||||
|
||||
// Server error
|
||||
if (response.statusCode >= 500) {
|
||||
sentryReportMessage(
|
||||
"Server error on GET request",
|
||||
context: {
|
||||
"url": url,
|
||||
"statusCode": "${response.statusCode}",
|
||||
"response": responseData.toString(),
|
||||
"params": params.toString(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
var data = await responseToJson(response);
|
||||
|
||||
return data;
|
||||
return responseData;
|
||||
}
|
||||
|
||||
Map<String, String> defaultHeaders() {
|
||||
@ -836,7 +947,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 +957,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, double? width}) {
|
||||
if (imageUrl.isEmpty) {
|
||||
imageUrl = staticImage;
|
||||
}
|
||||
|
125
lib/barcode.dart
125
lib/barcode.dart
@ -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,10 @@ class BarcodeScanHandler extends BarcodeHandler {
|
||||
L10().barcodeUnknown,
|
||||
success: false,
|
||||
onAction: () {
|
||||
showDialog(
|
||||
context: _context,
|
||||
|
||||
var _ctx = OneContext().context;
|
||||
|
||||
OneContext().showDialog(
|
||||
builder: (BuildContext context) => SimpleDialog(
|
||||
title: Text(L10().unknownResponse),
|
||||
children: <Widget>[
|
||||
@ -257,7 +283,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 +296,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 +308,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 +317,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 +380,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 +438,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 +494,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 +503,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 +515,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 +529,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 +543,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(
|
||||
|
@ -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,15 +142,11 @@ class InvenTreeModel {
|
||||
|
||||
|
||||
// Search this Model type in the database
|
||||
Future<List<InvenTreeModel>> search(BuildContext context, String searchTerm, {Map<String, String> filters}) async {
|
||||
|
||||
if (filters == null) {
|
||||
filters = {};
|
||||
}
|
||||
Future<List<InvenTreeModel>> search(BuildContext context, String searchTerm, {Map<String, String> filters = const {}}) async {
|
||||
|
||||
filters["search"] = searchTerm;
|
||||
|
||||
final results = list(context, filters: filters);
|
||||
final results = list(filters: filters);
|
||||
|
||||
return results;
|
||||
|
||||
@ -164,7 +160,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 +174,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 = const {}}) async {
|
||||
|
||||
var addr = path.join(URL, pk.toString());
|
||||
|
||||
@ -198,29 +194,27 @@ 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 = const {}}) 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();
|
||||
|
||||
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 +223,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 +235,6 @@ class InvenTreeModel {
|
||||
data.remove('id');
|
||||
}
|
||||
|
||||
InvenTreeModel _model;
|
||||
|
||||
var response = await api.post(URL, body: data);
|
||||
|
||||
if (response == null) {
|
||||
@ -252,13 +244,11 @@ 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 = const {}}) async {
|
||||
var params = defaultListFilters();
|
||||
|
||||
if (filters != null) {
|
||||
for (String key in filters.keys) {
|
||||
params[key] = filters[key];
|
||||
}
|
||||
params[key] = filters[key] ?? '';
|
||||
}
|
||||
|
||||
params["limit"] = "${limit}";
|
||||
@ -285,25 +275,17 @@ 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 {
|
||||
|
||||
if (filters == null) {
|
||||
filters = {};
|
||||
}
|
||||
Future<List<InvenTreeModel>> list({Map<String, String> filters = const {}}) async {
|
||||
|
||||
var params = defaultListFilters();
|
||||
|
||||
if (filters != null) {
|
||||
for (String key in filters.keys) {
|
||||
params[key] = filters[key];
|
||||
}
|
||||
params[key] = filters[key] ?? '';
|
||||
}
|
||||
|
||||
print("LIST: $URL ${params.toString()}");
|
||||
@ -311,7 +293,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 = [];
|
||||
|
||||
if (response == null) {
|
||||
return results;
|
||||
|
@ -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 = [];
|
||||
|
||||
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 = [];
|
||||
|
||||
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'].toString()) ?? 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'].toString()) ?? 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'].toString()) ?? 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 = [];
|
||||
|
||||
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();
|
||||
|
||||
|
@ -84,6 +84,40 @@ bool isInDebugMode() {
|
||||
return inDebugMode;
|
||||
}
|
||||
|
||||
Future<bool> sentryReportMessage(String message, {Map<String, String>? context}) async {
|
||||
|
||||
final server_info = getServerInfo();
|
||||
final app_info = await getAppInfo();
|
||||
final device_info = await getDeviceInfo();
|
||||
|
||||
print("Sending user message to Sentry: ${message}");
|
||||
|
||||
if (isInDebugMode()) {
|
||||
|
||||
print('----- In dev mode. Not sending message to Sentry.io -----');
|
||||
return true;
|
||||
}
|
||||
|
||||
Sentry.configureScope((scope) {
|
||||
scope.setExtra("server", server_info);
|
||||
scope.setExtra("app", app_info);
|
||||
scope.setExtra("device", device_info);
|
||||
|
||||
if (context != null) {
|
||||
scope.setExtra("context", context);
|
||||
}
|
||||
});
|
||||
|
||||
final sentryId = await Sentry.captureMessage(message).catchError((error) {
|
||||
print("Error uploading sentry messages...");
|
||||
print(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return sentryId != null;
|
||||
}
|
||||
|
||||
|
||||
Future<void> sentryReportError(dynamic error, dynamic stackTrace) async {
|
||||
|
||||
print('----- Sentry Intercepted error: $error -----');
|
||||
@ -115,27 +149,3 @@ Future<void> sentryReportError(dynamic error, dynamic stackTrace) async {
|
||||
print("Uploaded information to Sentry.io : ${response.toString()}");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Future<bool> sentryReportMessage(String message) async {
|
||||
|
||||
final server_info = getServerInfo();
|
||||
final app_info = await getAppInfo();
|
||||
final device_info = await getDeviceInfo();
|
||||
|
||||
print("Sending user message to Sentry");
|
||||
|
||||
Sentry.configureScope((scope) {
|
||||
scope.setExtra("server", server_info);
|
||||
scope.setExtra("app", app_info);
|
||||
scope.setExtra("device", device_info);
|
||||
});
|
||||
|
||||
final sentryId = await Sentry.captureMessage(message).catchError((error) {
|
||||
print("Error uploading sentry messages...");
|
||||
print(error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return sentryId != null;
|
||||
}
|
@ -138,14 +138,13 @@ class InvenTreeStockItem extends InvenTreeModel {
|
||||
// TODO
|
||||
}
|
||||
|
||||
List<InvenTreePartTestTemplate> testTemplates = List<InvenTreePartTestTemplate>();
|
||||
List<InvenTreePartTestTemplate> testTemplates = [];
|
||||
|
||||
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 = [];
|
||||
|
||||
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'].toString()) ?? 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,10 +398,10 @@ 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();
|
||||
return quantityString;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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);
|
||||
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;
|
||||
}
|
||||
|
||||
|
17
lib/l10.dart
17
lib/l10.dart
@ -1,9 +1,22 @@
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:one_context/one_context.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations_en.dart';
|
||||
|
||||
import 'package:one_context/one_context.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// Shortcut function to reduce boilerplate!
|
||||
I18N L10()
|
||||
{
|
||||
return I18N.of(OneContext().context);
|
||||
BuildContext? _ctx = OneContext().context;
|
||||
|
||||
if (_ctx != null) {
|
||||
I18N? i18n = I18N.of(_ctx);
|
||||
|
||||
if (i18n != null) {
|
||||
return i18n;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for "null" context
|
||||
return I18NEn();
|
||||
}
|
2
lib/l10n
2
lib/l10n
@ -1 +1 @@
|
||||
Subproject commit 05a5cbf63b4b5479162905def9fdadf21041212e
|
||||
Subproject commit 9cc07cdb0ec0012abcec827fc776d7c5473bb75e
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -26,7 +26,7 @@ class _InvenTreeLoginSettingsState extends State<InvenTreeLoginSettingsWidget> {
|
||||
|
||||
final GlobalKey<FormState> _addProfileKey = new GlobalKey<FormState>();
|
||||
|
||||
List<UserProfile> profiles;
|
||||
List<UserProfile> profiles = [];
|
||||
|
||||
_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)
|
||||
|
@ -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 = [];
|
||||
|
||||
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 = [];
|
||||
|
||||
for (int idx = 0; idx < profiles.length; idx++) {
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
import 'package:InvenTree/api.dart';
|
||||
import 'package:InvenTree/app_settings.dart';
|
||||
import 'package:InvenTree/inventree/part.dart';
|
||||
import 'package:InvenTree/inventree/sentry.dart';
|
||||
import 'package:InvenTree/widget/progress.dart';
|
||||
|
||||
import 'package:InvenTree/l10.dart';
|
||||
@ -23,9 +24,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 +82,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 +94,11 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||
|
||||
void _editCategoryDialog() {
|
||||
|
||||
// Cannot edit top-level category
|
||||
if (category == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var _name;
|
||||
var _description;
|
||||
|
||||
@ -108,12 +114,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 +129,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 = [];
|
||||
|
||||
@override
|
||||
Future<void> onBuild(BuildContext context) async {
|
||||
@ -133,17 +139,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 +174,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 +185,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 +296,8 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||
return ListView(
|
||||
children: actionTiles()
|
||||
);
|
||||
default:
|
||||
return ListView();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -306,7 +314,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 +356,7 @@ class PaginatedPartList extends StatefulWidget {
|
||||
|
||||
final Map<String, String> filters;
|
||||
|
||||
Function onTotalChanged;
|
||||
Function(int)? onTotalChanged;
|
||||
|
||||
PaginatedPartList(this.filters, {this.onTotalChanged});
|
||||
|
||||
@ -363,7 +371,7 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
|
||||
|
||||
String _searchTerm = "";
|
||||
|
||||
Function onTotalChanged;
|
||||
Function(int)? onTotalChanged;
|
||||
|
||||
final Map<String, String> filters;
|
||||
|
||||
@ -393,21 +401,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,22 +431,24 @@ class _PaginatedPartListState extends State<PaginatedPartList> {
|
||||
}
|
||||
|
||||
if (onTotalChanged != null) {
|
||||
onTotalChanged(pageCount);
|
||||
onTotalChanged!(pageCount);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
resultCount = pageCount;
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
} catch (error, stackTrace) {
|
||||
print("Error! - ${error.toString()}");
|
||||
_pagingController.error = error;
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
|
@ -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>();
|
||||
List<Widget> tiles = [];
|
||||
|
||||
bool sep = false;
|
||||
|
||||
|
@ -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>();
|
||||
List<InvenTreeCompany> _companies = [];
|
||||
|
||||
var _filteredCompanies = new List<InvenTreeCompany>();
|
||||
List<InvenTreeCompany> _filteredCompanies = [];
|
||||
|
||||
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) {
|
||||
InvenTreeCompany().get(company.pk).then((var c) {
|
||||
if (c != null && c is InvenTreeCompany) {
|
||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c)));
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -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,38 +170,44 @@ 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();
|
||||
|
||||
var _key = key;
|
||||
|
||||
if (_key != null && _key.currentState != null) {
|
||||
if (_key.currentState!.validate()) {
|
||||
_key.currentState!.save();
|
||||
|
||||
// Close the dialog
|
||||
Navigator.pop(dialogContext);
|
||||
var _ctx = dialogContext;
|
||||
|
||||
if (_ctx != null) {
|
||||
Navigator.pop(_ctx);
|
||||
}
|
||||
|
||||
// Callback
|
||||
if (callback != null) {
|
||||
@ -221,10 +215,13 @@ void showFormDialog(String title, {String acceptText, String cancelText, GlobalK
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _fields = fields ?? [];
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -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,11 +153,10 @@ 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.toString()) ?? 0;
|
||||
|
||||
if (quantity == null) return L10().quantityInvalid;
|
||||
if (quantity <= 0) return L10().quantityPositive;
|
||||
if ((max != null) && (quantity > max)) return "Quantity must not exceed ${max}";
|
||||
|
||||
|
@ -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.
|
||||
//
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:InvenTree/api.dart';
|
||||
import 'package:InvenTree/app_settings.dart';
|
||||
import 'package:InvenTree/barcode.dart';
|
||||
import 'package:InvenTree/inventree/sentry.dart';
|
||||
import 'package:InvenTree/inventree/stock.dart';
|
||||
import 'package:InvenTree/widget/progress.dart';
|
||||
|
||||
@ -20,11 +21,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 +33,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 +81,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;
|
||||
|
||||
if (location != null) {
|
||||
result = await location!.update(values: values);
|
||||
|
||||
showSnackIcon(
|
||||
result ? "Location edited" : "Location editing failed",
|
||||
success: result
|
||||
);
|
||||
}
|
||||
|
||||
refresh();
|
||||
}
|
||||
@ -95,6 +100,10 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||
var _name;
|
||||
var _description;
|
||||
|
||||
if (location == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
showFormDialog(L10().editLocation,
|
||||
key: _editLocationKey,
|
||||
callback: () {
|
||||
@ -106,12 +115,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 +129,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||
|
||||
_LocationDisplayState(this.location) {}
|
||||
|
||||
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
||||
List<InvenTreeStockLocation> _sublocations = [];
|
||||
|
||||
String _locationFilter = '';
|
||||
|
||||
@ -139,17 +148,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 +182,9 @@ 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}"),
|
||||
trailing: Text("${location!.itemcount}"),
|
||||
),
|
||||
];
|
||||
|
||||
@ -182,13 +192,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 +252,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 +270,7 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||
).toList()
|
||||
);
|
||||
default:
|
||||
return null;
|
||||
return ListView();
|
||||
}
|
||||
}
|
||||
|
||||
@ -308,14 +322,19 @@ List<Widget> detailTiles() {
|
||||
leading: FaIcon(FontAwesomeIcons.exchangeAlt),
|
||||
trailing: FaIcon(FontAwesomeIcons.qrcode),
|
||||
onTap: () {
|
||||
|
||||
var _loc = location;
|
||||
|
||||
if (_loc != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) =>
|
||||
InvenTreeQRView(
|
||||
StockLocationScanInItemsHandler(location)))
|
||||
StockLocationScanInItemsHandler(_loc)))
|
||||
).then((context) {
|
||||
refresh();
|
||||
});
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
@ -361,7 +380,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,35 +471,43 @@ 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 = [];
|
||||
|
||||
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) {
|
||||
} catch (error, stackTrace) {
|
||||
_pagingController.error = error;
|
||||
|
||||
sentryReportError(error, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
|
@ -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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -15,23 +15,30 @@ 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 = {};
|
||||
Map<String, String> _filters = {};
|
||||
|
||||
PartSearchDelegate(this.context, {this.filters}) {
|
||||
if (filters == null) {
|
||||
filters = {};
|
||||
PartSearchDelegate(this.context, {Map<String, String> filters = const {}}) {
|
||||
|
||||
// Copy filter values
|
||||
for (String key in filters.keys) {
|
||||
|
||||
String? value = filters[key];
|
||||
|
||||
if (value != null) {
|
||||
_filters[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,16 +69,15 @@ class PartSearchDelegate extends SearchDelegate<InvenTreePart> {
|
||||
|
||||
showResults(context);
|
||||
|
||||
// Enable cascading part search by default
|
||||
filters["cascade"] = "true";
|
||||
_filters["cascade"] = "true";
|
||||
|
||||
final results = await InvenTreePart().search(context, query, filters: filters);
|
||||
final results = await InvenTreePart().search(context, query, filters: _filters);
|
||||
|
||||
partResults.clear();
|
||||
|
||||
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 +138,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,22 +207,29 @@ 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) {
|
||||
filters = {};
|
||||
StockSearchDelegate(this.context, {Map<String, String> filters = const {}}) {
|
||||
|
||||
// Copy filter values
|
||||
for (String key in filters.keys) {
|
||||
|
||||
String? value = filters[key];
|
||||
|
||||
if (value != null) {
|
||||
_filters[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -247,16 +260,16 @@ class StockSearchDelegate extends SearchDelegate<InvenTreeStockItem> {
|
||||
showResults(context);
|
||||
|
||||
// Enable cascading part search by default
|
||||
filters["cascade"] = "true";
|
||||
_filters["cascade"] = "true";
|
||||
|
||||
final results = await InvenTreeStockItem().search(
|
||||
context, query, filters: filters);
|
||||
context, query, filters: _filters);
|
||||
|
||||
itemResults.clear();
|
||||
|
||||
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 +328,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,
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
if (parts != null) {
|
||||
for (int idx = 0; idx < parts.length; idx++) {
|
||||
if (parts[idx] is InvenTreePart) {
|
||||
starredParts.add(parts[idx]);
|
||||
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,
|
||||
|
@ -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>();
|
||||
List<InvenTreeStockLocation> suggestions = [];
|
||||
|
||||
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) {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
79
pubspec.lock
79
pubspec.lock
@ -29,6 +29,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.19.0"
|
||||
back_button_interceptor:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: back_button_interceptor
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.0.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -42,7 +49,7 @@ packages:
|
||||
name: cached_network_image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "3.0.0"
|
||||
camera:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -105,7 +112,7 @@ packages:
|
||||
name: cupertino_icons
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.3"
|
||||
version: "1.0.3"
|
||||
device_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -152,21 +159,35 @@ packages:
|
||||
name: flutter_blurhash
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "0.6.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "3.1.2"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_keyboard_visibility
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.3.0"
|
||||
version: "5.0.2"
|
||||
flutter_keyboard_visibility_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_keyboard_visibility_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
flutter_keyboard_visibility_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_keyboard_visibility_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -199,7 +220,7 @@ packages:
|
||||
name: flutter_speed_dial
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.5"
|
||||
version: "3.0.5"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -211,7 +232,7 @@ packages:
|
||||
name: flutter_typeahead
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.8.8"
|
||||
version: "3.1.3"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -223,7 +244,7 @@ packages:
|
||||
name: font_awesome_flutter
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.11.0"
|
||||
version: "9.1.0"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -272,7 +293,7 @@ packages:
|
||||
name: infinite_scroll_pagination
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "3.1.0"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -314,14 +335,14 @@ packages:
|
||||
name: octo_image
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.0"
|
||||
version: "1.0.0+1"
|
||||
one_context:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: one_context
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
version: "1.1.0"
|
||||
package_info:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -454,7 +475,7 @@ packages:
|
||||
name: qr_code_scanner
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.5"
|
||||
version: "0.5.1"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -468,14 +489,14 @@ packages:
|
||||
name: rxdart
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.25.0"
|
||||
version: "0.27.1"
|
||||
sembast:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sembast
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.4.9"
|
||||
version: "3.1.0+2"
|
||||
sentry:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -496,28 +517,42 @@ packages:
|
||||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.7+3"
|
||||
version: "2.0.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.0.1+11"
|
||||
version: "2.0.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "2.0.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.2+7"
|
||||
version: "2.0.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -529,7 +564,7 @@ packages:
|
||||
name: sliver_tools
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.1.10+1"
|
||||
version: "0.2.5"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -543,14 +578,14 @@ packages:
|
||||
name: sqflite
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.3.2+3"
|
||||
version: "2.0.0+3"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.3+1"
|
||||
version: "2.0.0+2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -585,7 +620,7 @@ packages:
|
||||
name: synchronized
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.2.0+2"
|
||||
version: "3.0.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
26
pubspec.yaml
26
pubspec.yaml
@ -7,10 +7,10 @@ description: InvenTree stock management
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.2.5+13
|
||||
version: 0.2.6+14
|
||||
|
||||
environment:
|
||||
sdk: ">=2.1.0 <3.0.0"
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
@ -21,25 +21,25 @@ dependencies:
|
||||
|
||||
intl: ^0.17.0
|
||||
|
||||
cupertino_icons: ^0.1.3
|
||||
cupertino_icons: ^1.0.3
|
||||
http: ^0.13.0
|
||||
cached_network_image: ^2.5.0
|
||||
qr_code_scanner: ^0.3.5 # Barcode scanning
|
||||
cached_network_image: ^3.0.0 # Download and cache remote images
|
||||
qr_code_scanner: ^0.5.1 # 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
|
||||
font_awesome_flutter: ^9.1.0 # FontAwesome icon set
|
||||
flutter_speed_dial: ^3.0.5 # FAB menu elements
|
||||
sentry_flutter: 5.0.0 # Error reporting
|
||||
flutter_typeahead: ^1.8.1 # Auto-complete input field
|
||||
flutter_typeahead: ^3.1.0 # Auto-complete input field
|
||||
image_picker: ^0.8.0 # Select or take photos
|
||||
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
|
||||
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
|
||||
path_provider: 2.0.1 # Local file storage
|
||||
sembast: ^3.1.0+2 # NoSQL data storage
|
||||
one_context: ^1.1.0 # Dialogs without requiring context
|
||||
infinite_scroll_pagination: ^3.1.0 # Let the server do all the work!
|
||||
audioplayers: ^0.19.0 # Play audio files
|
||||
path:
|
||||
|
||||
dev_dependencies:
|
||||
|
Loading…
x
Reference in New Issue
Block a user