mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-29 05:56:47 +00:00
Merge branch 'barcodez'
This commit is contained in:
commit
54d6e1c6d3
13
lib/api.dart
13
lib/api.dart
@ -182,15 +182,22 @@ class InvenTreeAPI {
|
|||||||
var response = await get("").timeout(Duration(seconds: 10)).catchError((error) {
|
var response = await get("").timeout(Duration(seconds: 10)).catchError((error) {
|
||||||
|
|
||||||
if (error is SocketException) {
|
if (error is SocketException) {
|
||||||
errorMessage = "Could not connect to server.";
|
print("Could not connect to server");
|
||||||
print(errorMessage);
|
return null;
|
||||||
throw errorMessage;
|
} else if (error is TimeoutException) {
|
||||||
|
print("Server timeout");
|
||||||
|
return null;
|
||||||
} else {
|
} else {
|
||||||
// Unknown error type, re-throw error
|
// Unknown error type, re-throw error
|
||||||
|
print("Unknown error: ${error.toString()}");
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
print("Invalid status code: " + response.statusCode.toString());
|
print("Invalid status code: " + response.statusCode.toString());
|
||||||
return false;
|
return false;
|
||||||
|
163
lib/barcode.dart
163
lib/barcode.dart
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:qr_utils/qr_utils.dart';
|
import 'package:qr_utils/qr_utils.dart';
|
||||||
@ -5,6 +6,8 @@ import 'package:qr_utils/qr_utils.dart';
|
|||||||
import 'package:InvenTree/inventree/stock.dart';
|
import 'package:InvenTree/inventree/stock.dart';
|
||||||
import 'package:InvenTree/inventree/part.dart';
|
import 'package:InvenTree/inventree/part.dart';
|
||||||
|
|
||||||
|
import 'package:InvenTree/api.dart';
|
||||||
|
|
||||||
import 'package:InvenTree/widget/location_display.dart';
|
import 'package:InvenTree/widget/location_display.dart';
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_detail.dart';
|
||||||
import 'package:InvenTree/widget/category_display.dart';
|
import 'package:InvenTree/widget/category_display.dart';
|
||||||
@ -12,59 +15,139 @@ import 'package:InvenTree/widget/stock_detail.dart';
|
|||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
void scanQrCode(BuildContext context) async {
|
Future<void> scanQrCode(BuildContext context) async {
|
||||||
|
|
||||||
QrUtils.scanQR.then((String result) {
|
QrUtils.scanQR.then((String barcode) {
|
||||||
|
|
||||||
print("Scanned: $result");
|
print("Scanned: $barcode");
|
||||||
|
|
||||||
// Look for JSON data in the result...
|
showProgressDialog(context, "Querying Server", "Sending barcode data to server");
|
||||||
final data = json.decode(result);
|
|
||||||
|
|
||||||
// Look for an 'InvenTree' style barcode
|
/*
|
||||||
if ((data['tool'] ?? '').toString().toLowerCase() == 'inventree') {
|
* POST the scanned barcode data to the server.
|
||||||
_handleInvenTreeBarcode(context, data);
|
* It is the responsibility of the server to validate and sanitize the barcode data,
|
||||||
}
|
* and return a "common" response that we know how to deal with.
|
||||||
|
*/
|
||||||
|
InvenTreeAPI().post("barcode/", body: {"barcode": barcode}).then((var response) {
|
||||||
|
|
||||||
// Unknown barcode style!
|
hideProgressDialog(context);
|
||||||
else {
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
child: new SimpleDialog(
|
|
||||||
title: new Text("Unknown barcode"),
|
|
||||||
children: <Widget>[
|
|
||||||
Text("Data: $result"),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (response.statusCode != 200) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: new SimpleDialog(
|
||||||
|
title: Text("Server Error"),
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text("Error ${response.statusCode}"),
|
||||||
|
subtitle: Text("${response.body.toString().split("\n").first}"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Map<String, dynamic> body = json.decode(response.body);
|
||||||
|
|
||||||
|
// TODO - Handle potential error decoding response
|
||||||
|
|
||||||
|
print("Barcode response:");
|
||||||
|
print(body.toString());
|
||||||
|
|
||||||
|
if (body.containsKey('error')) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: new SimpleDialog(
|
||||||
|
title: Text("Barcode Error"),
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text("${body['error']}"),
|
||||||
|
subtitle: Text("Plugin: ${body['plugin'] ?? '<no plugin information>'}"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else if (body.containsKey('success')) {
|
||||||
|
// Decode the barcode!
|
||||||
|
// Ideally, the server has returned unto us something sensible...
|
||||||
|
_handleBarcode(context, body);
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: new SimpleDialog(
|
||||||
|
title: Text("Unknown response"),
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text("Response data"),
|
||||||
|
subtitle: Text("${body.toString()}"),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("body: ${body.toString()}");
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleInvenTreeBarcode(BuildContext context, Map<String, dynamic> data) {
|
void _handleBarcode(BuildContext context, Map<String, dynamic> data) {
|
||||||
|
|
||||||
final String codeType = (data['type'] ?? '').toString().toLowerCase();
|
int pk;
|
||||||
|
|
||||||
final int pk = (data['id'] ?? -1) as int;
|
// A stocklocation has been passed?
|
||||||
|
if (data.containsKey('stocklocation')) {
|
||||||
|
|
||||||
if (codeType == 'stocklocation') {
|
pk = data['stocklocation']['pk'] as int ?? null;
|
||||||
|
|
||||||
// Try to open a stock location...
|
if (pk != null) {
|
||||||
InvenTreeStockLocation().get(pk).then((var loc) {
|
InvenTreeStockLocation().get(context, pk).then((var loc) {
|
||||||
if (loc is InvenTreeStockLocation) {
|
if (loc is InvenTreeStockLocation) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// TODO - Show an error here!
|
||||||
|
}
|
||||||
|
|
||||||
} else if (codeType == 'stockitem') {
|
} else if (data.containsKey('stockitem')) {
|
||||||
InvenTreeStockItem().get(pk).then((var item) {
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
pk = data['stockitem']['pk'] as int ?? null;
|
||||||
});
|
|
||||||
} else if (codeType == 'part') {
|
if (pk != null) {
|
||||||
InvenTreePart().get(pk).then((var part) {
|
InvenTreeStockItem().get(context, pk).then((var item) {
|
||||||
Navigator.push(context,
|
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
||||||
MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
});
|
||||||
});
|
} else {
|
||||||
|
// TODO - Show an error here!
|
||||||
|
}
|
||||||
|
} else if (data.containsKey('part')) {
|
||||||
|
|
||||||
|
pk = data['part']['pk'] as int ?? null;
|
||||||
|
|
||||||
|
if (pk != null) {
|
||||||
|
InvenTreePart().get(context, pk).then((var part) {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO - Show an error here!
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: SimpleDialog(
|
||||||
|
title: Text("Unknown response"),
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text("Response data"),
|
||||||
|
subtitle: Text(data.toString()),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,6 +5,10 @@ import 'model.dart';
|
|||||||
* The InvenTreeCompany class repreents the Company model in the InvenTree database.
|
* The InvenTreeCompany class repreents the Company model in the InvenTree database.
|
||||||
*/
|
*/
|
||||||
class InvenTreeCompany extends InvenTreeModel {
|
class InvenTreeCompany extends InvenTreeModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String NAME = "Company";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String URL = "company/";
|
String URL = "company/";
|
||||||
|
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
|
import 'package:InvenTree/widget/dialogs.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
@ -15,6 +19,8 @@ class InvenTreeModel {
|
|||||||
// Override the endpoint URL for each subclass
|
// Override the endpoint URL for each subclass
|
||||||
String URL = "";
|
String URL = "";
|
||||||
|
|
||||||
|
String NAME = "Model";
|
||||||
|
|
||||||
// JSON data which defines this object
|
// JSON data which defines this object
|
||||||
Map<String, dynamic> jsondata = {};
|
Map<String, dynamic> jsondata = {};
|
||||||
|
|
||||||
@ -83,9 +89,30 @@ class InvenTreeModel {
|
|||||||
/*
|
/*
|
||||||
* Reload this object, by requesting data from the server
|
* Reload this object, by requesting data from the server
|
||||||
*/
|
*/
|
||||||
Future<bool> reload() async {
|
Future<bool> reload(BuildContext context) async {
|
||||||
|
|
||||||
var response = await api.get(url, params: defaultGetFilters());
|
showProgressDialog(context, "Refreshing data", "Refreshing data for ${NAME}");
|
||||||
|
|
||||||
|
var response = await api.get(url, params: defaultGetFilters())
|
||||||
|
.timeout(Duration(seconds: 10))
|
||||||
|
.catchError((e) {
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
showErrorDialog(context, "Timeout", "No response from server");
|
||||||
|
} else {
|
||||||
|
showErrorDialog(context, "Error", e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
print("Error retrieving data");
|
print("Error retrieving data");
|
||||||
@ -100,7 +127,7 @@ class InvenTreeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return the detail view for the associated pk
|
// Return the detail view for the associated pk
|
||||||
Future<InvenTreeModel> get(int pk, {Map<String, String> filters}) async {
|
Future<InvenTreeModel> get(BuildContext context, int pk, {Map<String, String> filters}) async {
|
||||||
|
|
||||||
// TODO - Add "timeout"
|
// TODO - Add "timeout"
|
||||||
// TODO - Add error catching
|
// TODO - Add error catching
|
||||||
@ -122,7 +149,27 @@ class InvenTreeModel {
|
|||||||
|
|
||||||
print("GET: $addr ${params.toString()}");
|
print("GET: $addr ${params.toString()}");
|
||||||
|
|
||||||
var response = await api.get(addr, params: params);
|
showProgressDialog(context, "Requesting Data", "Requesting ${NAME} data from server");
|
||||||
|
|
||||||
|
var response = await api.get(addr, params: params)
|
||||||
|
.timeout(Duration(seconds: 10))
|
||||||
|
.catchError((e) {
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
showErrorDialog(context, "Timeout", "No response from server");
|
||||||
|
} else {
|
||||||
|
showErrorDialog(context, "Error", e.toString());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
if (response.statusCode != 200) {
|
if (response.statusCode != 200) {
|
||||||
print("Error retrieving data");
|
print("Error retrieving data");
|
||||||
@ -135,7 +182,7 @@ class InvenTreeModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Return list of objects from the database, with optional filters
|
// Return list of objects from the database, with optional filters
|
||||||
Future<List<InvenTreeModel>> list({Map<String, String> filters}) async {
|
Future<List<InvenTreeModel>> list(BuildContext context, {Map<String, String> filters}) async {
|
||||||
|
|
||||||
if (filters == null) {
|
if (filters == null) {
|
||||||
filters = {};
|
filters = {};
|
||||||
@ -154,7 +201,28 @@ class InvenTreeModel {
|
|||||||
// TODO - Add "timeout"
|
// TODO - Add "timeout"
|
||||||
// TODO - Add error catching
|
// TODO - Add error catching
|
||||||
|
|
||||||
var response = await api.get(URL, params:params);
|
showProgressDialog(context, "Requesting Data", "Requesting ${NAME} data from server");
|
||||||
|
|
||||||
|
var response = await api.get(URL, params:params)
|
||||||
|
.timeout(Duration(seconds: 10))
|
||||||
|
.catchError((e) {
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
|
if (e is TimeoutException) {
|
||||||
|
showErrorDialog(context, "Timeout", "No response from server");
|
||||||
|
} else {
|
||||||
|
showErrorDialog(context, "Error", e.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hideProgressDialog(context);
|
||||||
|
|
||||||
// A list of "InvenTreeModel" items
|
// A list of "InvenTreeModel" items
|
||||||
List<InvenTreeModel> results = new List<InvenTreeModel>();
|
List<InvenTreeModel> results = new List<InvenTreeModel>();
|
||||||
|
@ -9,6 +9,10 @@ import 'package:path/path.dart' as path;
|
|||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
|
||||||
class InvenTreePartCategory extends InvenTreeModel {
|
class InvenTreePartCategory extends InvenTreeModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String NAME = "PartCategory";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String URL = "part/category/";
|
String URL = "part/category/";
|
||||||
|
|
||||||
@ -61,6 +65,9 @@ class InvenTreePartCategory extends InvenTreeModel {
|
|||||||
|
|
||||||
class InvenTreePart extends InvenTreeModel {
|
class InvenTreePart extends InvenTreeModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String NAME = "Part";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String URL = "part/";
|
String URL = "part/";
|
||||||
|
|
||||||
|
@ -6,6 +6,10 @@ import 'model.dart';
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
|
|
||||||
class InvenTreeStockItem extends InvenTreeModel {
|
class InvenTreeStockItem extends InvenTreeModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String NAME = "StockItem";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String URL = "stock/";
|
String URL = "stock/";
|
||||||
|
|
||||||
@ -208,6 +212,10 @@ class InvenTreeStockItem extends InvenTreeModel {
|
|||||||
|
|
||||||
|
|
||||||
class InvenTreeStockLocation extends InvenTreeModel {
|
class InvenTreeStockLocation extends InvenTreeModel {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String NAME = "StockLocation";
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String URL = "stock/location/";
|
String URL = "stock/location/";
|
||||||
|
|
||||||
|
@ -22,6 +22,8 @@ import 'preferences.dart';
|
|||||||
|
|
||||||
import 'package:InvenTree/inventree/part.dart';
|
import 'package:InvenTree/inventree/part.dart';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
|
|
||||||
// await PrefService.init(prefix: "inventree_");
|
// await PrefService.init(prefix: "inventree_");
|
||||||
@ -31,10 +33,10 @@ void main() async {
|
|||||||
// Load login details
|
// Load login details
|
||||||
InvenTreePreferences().loadLoginDetails();
|
InvenTreePreferences().loadLoginDetails();
|
||||||
|
|
||||||
runApp(MyApp());
|
runApp(InvenTreeApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class InvenTreeApp extends StatelessWidget {
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -42,16 +44,8 @@ class MyApp extends StatelessWidget {
|
|||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
title: 'InvenTree',
|
title: 'InvenTree',
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
// This is the theme of your application.
|
primarySwatch: Colors.lightBlue,
|
||||||
//
|
secondaryHeaderColor: Colors.blueGrey,
|
||||||
// Try running your application with "flutter run". You'll see the
|
|
||||||
// application has a blue toolbar. Then, without quitting the app, try
|
|
||||||
// changing the primarySwatch below to Colors.green and then invoke
|
|
||||||
// "hot reload" (press "r" in the console where you ran "flutter run",
|
|
||||||
// or simply save your changes to "hot reload" in a Flutter IDE).
|
|
||||||
// Notice that the counter didn't reset back to zero; the application
|
|
||||||
// is not restarted.
|
|
||||||
primarySwatch: Colors.lightGreen,
|
|
||||||
),
|
),
|
||||||
home: MyHomePage(title: 'InvenTree'),
|
home: MyHomePage(title: 'InvenTree'),
|
||||||
);
|
);
|
||||||
@ -59,34 +53,6 @@ class MyApp extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ProductList extends StatelessWidget {
|
|
||||||
final List<InvenTreePart> _parts;
|
|
||||||
|
|
||||||
ProductList(this._parts);
|
|
||||||
|
|
||||||
Widget _buildPart(BuildContext context, int index) {
|
|
||||||
InvenTreePart part;
|
|
||||||
|
|
||||||
if (index < _parts.length) {
|
|
||||||
part = _parts[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return Card(
|
|
||||||
child: Column(
|
|
||||||
children: <Widget>[
|
|
||||||
Text('${part.name} - ${part.description}'),
|
|
||||||
]
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ListView.builder(itemBuilder: _buildPart, itemCount: _parts.length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class MyHomePage extends StatefulWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
MyHomePage({Key key, this.title}) : super(key: key);
|
MyHomePage({Key key, this.title}) : super(key: key);
|
||||||
|
|
||||||
@ -168,22 +134,6 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
onConnectFailure("Could not connect to server");
|
onConnectFailure("Could not connect to server");
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catchError((e) {
|
|
||||||
|
|
||||||
String fault = "Connection error";
|
|
||||||
|
|
||||||
_serverConnection = false;
|
|
||||||
_serverStatusColor = Color.fromARGB(255, 250, 50, 50);
|
|
||||||
|
|
||||||
_serverStatus = "Error connecting to $_serverAddress";
|
|
||||||
|
|
||||||
if (e is TimeoutException) {
|
|
||||||
fault = "Timeout: No response from server";
|
|
||||||
} else {
|
|
||||||
fault = e.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
onConnectFailure(fault);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update widget state
|
// Update widget state
|
||||||
|
@ -5,6 +5,7 @@ import 'package:InvenTree/preferences.dart';
|
|||||||
|
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_detail.dart';
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -26,11 +27,12 @@ class CategoryDisplayWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _CategoryDisplayState extends State<CategoryDisplayWidget> {
|
class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
||||||
|
|
||||||
_CategoryDisplayState(this.category) {
|
@override
|
||||||
_requestData();
|
String getAppBarTitle(BuildContext context) { return "Part Category"; }
|
||||||
}
|
|
||||||
|
_CategoryDisplayState(this.category) {}
|
||||||
|
|
||||||
// The local InvenTreePartCategory object
|
// The local InvenTreePartCategory object
|
||||||
final InvenTreePartCategory category;
|
final InvenTreePartCategory category;
|
||||||
@ -39,24 +41,18 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
|
|||||||
|
|
||||||
List<InvenTreePart> _parts = List<InvenTreePart>();
|
List<InvenTreePart> _parts = List<InvenTreePart>();
|
||||||
|
|
||||||
String get _titleString {
|
@override
|
||||||
|
Future<void> onBuild(BuildContext context) async {
|
||||||
if (category == null) {
|
refresh();
|
||||||
return "Part Categories";
|
|
||||||
} else {
|
|
||||||
return "Part Category - ${category.name}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
@override
|
||||||
* Request data from the server
|
Future<void> request(BuildContext context) async {
|
||||||
*/
|
|
||||||
void _requestData() {
|
|
||||||
|
|
||||||
int pk = category?.pk ?? -1;
|
int pk = category?.pk ?? -1;
|
||||||
|
|
||||||
// Request a list of sub-categories under this one
|
// Request a list of sub-categories under this one
|
||||||
InvenTreePartCategory().list(filters: {"parent": "$pk"}).then((var cats) {
|
InvenTreePartCategory().list(context, filters: {"parent": "$pk"}).then((var cats) {
|
||||||
_subcategories.clear();
|
_subcategories.clear();
|
||||||
|
|
||||||
for (var cat in cats) {
|
for (var cat in cats) {
|
||||||
@ -70,7 +66,7 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Request a list of parts under this category
|
// Request a list of parts under this category
|
||||||
InvenTreePart().list(filters: {"category": "$pk"}).then((var parts) {
|
InvenTreePart().list(context, filters: {"category": "$pk"}).then((var parts) {
|
||||||
_parts.clear();
|
_parts.clear();
|
||||||
|
|
||||||
for (var part in parts) {
|
for (var part in parts) {
|
||||||
@ -112,7 +108,7 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
|
|||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
|
||||||
} else {
|
} else {
|
||||||
// TODO - Refactor this code into the InvenTreePart class
|
// TODO - Refactor this code into the InvenTreePart class
|
||||||
InvenTreePartCategory().get(category.parentId).then((var cat) {
|
InvenTreePartCategory().get(context, category.parentId).then((var cat) {
|
||||||
if (cat is InvenTreePartCategory) {
|
if (cat is InvenTreePartCategory) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
||||||
}
|
}
|
||||||
@ -127,74 +123,68 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return Scaffold(
|
return ListView(
|
||||||
appBar: AppBar(
|
children: <Widget>[
|
||||||
title: Text(_titleString),
|
getCategoryDescriptionCard(),
|
||||||
),
|
ExpansionPanelList(
|
||||||
drawer: new InvenTreeDrawer(context),
|
expansionCallback: (int index, bool isExpanded) {
|
||||||
body: ListView(
|
setState(() {
|
||||||
children: <Widget>[
|
|
||||||
getCategoryDescriptionCard(),
|
|
||||||
ExpansionPanelList(
|
|
||||||
expansionCallback: (int index, bool isExpanded) {
|
|
||||||
setState(() {
|
|
||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
case 0:
|
case 0:
|
||||||
InvenTreePreferences().expandCategoryList = !isExpanded;
|
InvenTreePreferences().expandCategoryList = !isExpanded;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
InvenTreePreferences().expandPartList = !isExpanded;
|
InvenTreePreferences().expandPartList = !isExpanded;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
children: <ExpansionPanel> [
|
||||||
|
ExpansionPanel(
|
||||||
|
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text("Subcategories"),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.stream),
|
||||||
|
trailing: Text("${_subcategories.length}"),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
InvenTreePreferences().expandCategoryList = !InvenTreePreferences().expandCategoryList;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
// TODO - Context menu for e.g. creating a new PartCategory
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
children: <ExpansionPanel> [
|
body: SubcategoryList(_subcategories),
|
||||||
ExpansionPanel(
|
isExpanded: InvenTreePreferences().expandCategoryList && _subcategories.length > 0,
|
||||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text("Subcategories"),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.stream),
|
|
||||||
trailing: Text("${_subcategories.length}"),
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
InvenTreePreferences().expandCategoryList = !InvenTreePreferences().expandCategoryList;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
// TODO - Context menu for e.g. creating a new PartCategory
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
body: SubcategoryList(_subcategories),
|
|
||||||
isExpanded: InvenTreePreferences().expandCategoryList && _subcategories.length > 0,
|
|
||||||
),
|
|
||||||
ExpansionPanel(
|
|
||||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
|
||||||
return ListTile(
|
|
||||||
title: Text("Parts"),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.shapes),
|
|
||||||
trailing: Text("${_parts.length}"),
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
InvenTreePreferences().expandPartList = !InvenTreePreferences().expandPartList;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
onLongPress: () {
|
|
||||||
// TODO - Context menu for e.g. creating a new Part
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
body: PartList(_parts),
|
|
||||||
isExpanded: InvenTreePreferences().expandPartList && _parts.length > 0,
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
]
|
ExpansionPanel(
|
||||||
)
|
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text("Parts"),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.shapes),
|
||||||
|
trailing: Text("${_parts.length}"),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
InvenTreePreferences().expandPartList = !InvenTreePreferences().expandPartList;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
onLongPress: () {
|
||||||
|
// TODO - Context menu for e.g. creating a new Part
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
body: PartList(_parts),
|
||||||
|
isExpanded: InvenTreePreferences().expandPartList && _parts.length > 0,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -211,7 +201,7 @@ class SubcategoryList extends StatelessWidget {
|
|||||||
void _openCategory(BuildContext context, int pk) {
|
void _openCategory(BuildContext context, int pk) {
|
||||||
|
|
||||||
// Attempt to load the sub-category.
|
// Attempt to load the sub-category.
|
||||||
InvenTreePartCategory().get(pk).then((var cat) {
|
InvenTreePartCategory().get(context, pk).then((var cat) {
|
||||||
if (cat is InvenTreePartCategory) {
|
if (cat is InvenTreePartCategory) {
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
||||||
@ -252,7 +242,7 @@ class PartList extends StatelessWidget {
|
|||||||
|
|
||||||
void _openPart(BuildContext context, int pk) {
|
void _openPart(BuildContext context, int pk) {
|
||||||
// Attempt to load the part information
|
// Attempt to load the part information
|
||||||
InvenTreePart().get(pk).then((var part) {
|
InvenTreePart().get(context, pk).then((var part) {
|
||||||
if (part is InvenTreePart) {
|
if (part is InvenTreePart) {
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
import 'package:InvenTree/inventree/company.dart';
|
import 'package:InvenTree/inventree/company.dart';
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
@ -18,10 +19,18 @@ class CompanyDetailWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _CompanyDetailState extends State<CompanyDetailWidget> {
|
class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
|
||||||
|
|
||||||
final InvenTreeCompany company;
|
final InvenTreeCompany company;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle(BuildContext context) { return "Company"; }
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> request(BuildContext context) async {
|
||||||
|
await company.reload(context);
|
||||||
|
}
|
||||||
|
|
||||||
_CompanyDetailState(this.company) {
|
_CompanyDetailState(this.company) {
|
||||||
// TODO
|
// TODO
|
||||||
}
|
}
|
||||||
@ -122,17 +131,11 @@ class _CompanyDetailState extends State<CompanyDetailWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
|
||||||
return Scaffold(
|
return Center(
|
||||||
appBar: AppBar(
|
child: ListView(
|
||||||
title: Text("${company.name}"),
|
children: _companyTiles(),
|
||||||
),
|
|
||||||
drawer: new InvenTreeDrawer(context),
|
|
||||||
body: Center(
|
|
||||||
child: ListView(
|
|
||||||
children: _companyTiles(),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
import 'package:InvenTree/inventree/company.dart';
|
import 'package:InvenTree/inventree/company.dart';
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
abstract class CompanyListWidget extends StatefulWidget {
|
abstract class CompanyListWidget extends StatefulWidget {
|
||||||
@ -30,23 +31,30 @@ class CustomerListWidget extends CompanyListWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _CompanyListState extends State<CompanyListWidget> {
|
class _CompanyListState extends RefreshableState<CompanyListWidget> {
|
||||||
|
|
||||||
var _companies = new List<InvenTreeCompany>();
|
var _companies = new List<InvenTreeCompany>();
|
||||||
|
|
||||||
var _filteredCompanies = new List<InvenTreeCompany>();
|
var _filteredCompanies = new List<InvenTreeCompany>();
|
||||||
|
|
||||||
var _title = "Companies";
|
String _title = "Companies";
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle(BuildContext context) { return _title; }
|
||||||
|
|
||||||
Map<String, String> _filters = Map<String, String>();
|
Map<String, String> _filters = Map<String, String>();
|
||||||
|
|
||||||
_CompanyListState(this._title, this._filters) {
|
_CompanyListState(this._title, this._filters) {}
|
||||||
_requestData();
|
|
||||||
|
@override
|
||||||
|
Future<void> onBuild(BuildContext context) async {
|
||||||
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _requestData() {
|
@override
|
||||||
|
Future<void> request(BuildContext context) async {
|
||||||
|
|
||||||
InvenTreeCompany().list(filters: _filters).then((var companies) {
|
InvenTreeCompany().list(context, filters: _filters).then((var companies) {
|
||||||
|
|
||||||
_companies.clear();
|
_companies.clear();
|
||||||
|
|
||||||
@ -85,7 +93,7 @@ class _CompanyListState extends State<CompanyListWidget> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (company.pk > 0) {
|
if (company.pk > 0) {
|
||||||
InvenTreeCompany().get(company.pk).then((var c) {
|
InvenTreeCompany().get(context, company.pk).then((var c) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CompanyDetailWidget(c)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -94,38 +102,24 @@ class _CompanyListState extends State<CompanyListWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return Scaffold(
|
return ListView(
|
||||||
appBar: AppBar(
|
children: <Widget>[
|
||||||
title: Text("$_title"),
|
TextField(
|
||||||
actions: <Widget>[
|
decoration: InputDecoration(
|
||||||
IconButton(
|
hintText: 'Filter results',
|
||||||
icon: FaIcon(FontAwesomeIcons.plus),
|
),
|
||||||
tooltip: 'New',
|
onChanged: (String text) {
|
||||||
onPressed: null,
|
setState(() {
|
||||||
)
|
_filterResults(text);
|
||||||
],
|
});
|
||||||
|
},
|
||||||
),
|
),
|
||||||
drawer: new InvenTreeDrawer(context),
|
ListView.builder(
|
||||||
body: ListView(
|
shrinkWrap: true,
|
||||||
children: <Widget>[
|
physics: ClampingScrollPhysics(),
|
||||||
TextField(
|
itemBuilder: _showCompany, itemCount: _filteredCompanies.length)
|
||||||
decoration: InputDecoration(
|
],
|
||||||
hintText: 'Filter results',
|
|
||||||
),
|
|
||||||
onChanged: (String text) {
|
|
||||||
setState(() {
|
|
||||||
_filterResults(text);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: ClampingScrollPhysics(),
|
|
||||||
itemBuilder: _showCompany, itemCount: _filteredCompanies.length)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
41
lib/widget/dialogs.dart
Normal file
41
lib/widget/dialogs.dart
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
void showErrorDialog(BuildContext context, String title, String description) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
child: SimpleDialog(
|
||||||
|
title: ListTile(
|
||||||
|
title: Text("Error"),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.exclamationCircle),
|
||||||
|
),
|
||||||
|
children: <Widget>[
|
||||||
|
ListTile(
|
||||||
|
title: Text(title),
|
||||||
|
subtitle: Text(description)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showProgressDialog(BuildContext context, String title, String description) {
|
||||||
|
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
child: SimpleDialog(
|
||||||
|
title: Text(title),
|
||||||
|
children: <Widget>[
|
||||||
|
CircularProgressIndicator(),
|
||||||
|
Text(description),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void hideProgressDialog(BuildContext context) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
|
|
||||||
class LocationDisplayWidget extends StatefulWidget {
|
class LocationDisplayWidget extends StatefulWidget {
|
||||||
|
|
||||||
LocationDisplayWidget(this.location, {Key key}) : super(key: key);
|
LocationDisplayWidget(this.location, {Key key}) : super(key: key);
|
||||||
@ -20,15 +22,15 @@ class LocationDisplayWidget extends StatefulWidget {
|
|||||||
_LocationDisplayState createState() => _LocationDisplayState(location);
|
_LocationDisplayState createState() => _LocationDisplayState(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
||||||
class _LocationDisplayState extends State<LocationDisplayWidget> {
|
|
||||||
|
|
||||||
_LocationDisplayState(this.location) {
|
|
||||||
_requestData();
|
|
||||||
}
|
|
||||||
|
|
||||||
final InvenTreeStockLocation location;
|
final InvenTreeStockLocation location;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
|
||||||
|
|
||||||
|
_LocationDisplayState(this.location) {}
|
||||||
|
|
||||||
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
|
||||||
|
|
||||||
String _locationFilter = '';
|
String _locationFilter = '';
|
||||||
@ -44,28 +46,18 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
|
|||||||
|
|
||||||
List<InvenTreeStockItem> _items = List<InvenTreeStockItem>();
|
List<InvenTreeStockItem> _items = List<InvenTreeStockItem>();
|
||||||
|
|
||||||
String get _title {
|
@override
|
||||||
|
Future<void> onBuild(BuildContext context) async {
|
||||||
if (location == null) {
|
refresh();
|
||||||
return "Stock Locations";
|
|
||||||
} else {
|
|
||||||
return "Stock Location - ${location.name}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
@override
|
||||||
* Request data from the server.
|
Future<void> request(BuildContext context) async {
|
||||||
* It will be displayed once loaded
|
|
||||||
*
|
|
||||||
* - List of sublocations under this one
|
|
||||||
* - List of stock items at this location
|
|
||||||
*/
|
|
||||||
void _requestData() {
|
|
||||||
|
|
||||||
int pk = location?.pk ?? -1;
|
int pk = location?.pk ?? -1;
|
||||||
|
|
||||||
// Request a list of sub-locations under this one
|
// Request a list of sub-locations under this one
|
||||||
InvenTreeStockLocation().list(filters: {"parent": "$pk"}).then((var locs) {
|
InvenTreeStockLocation().list(context, filters: {"parent": "$pk"}).then((var locs) {
|
||||||
_sublocations.clear();
|
_sublocations.clear();
|
||||||
|
|
||||||
for (var loc in locs) {
|
for (var loc in locs) {
|
||||||
@ -76,18 +68,18 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
|
|||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
|
||||||
// Request a list of stock-items under this one
|
// Request a list of stock-items under this one
|
||||||
InvenTreeStockItem().list(filters: {"location": "$pk"}).then((var items) {
|
InvenTreeStockItem().list(context, filters: {"location": "$pk"}).then((var items) {
|
||||||
_items.clear();
|
_items.clear();
|
||||||
|
|
||||||
for (var item in items) {
|
for (var item in items) {
|
||||||
if (item is InvenTreeStockItem) {
|
if (item is InvenTreeStockItem) {
|
||||||
_items.add(item);
|
_items.add(item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -119,7 +111,7 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
|
|||||||
if (location.parentId < 0) {
|
if (location.parentId < 0) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
||||||
} else {
|
} else {
|
||||||
InvenTreeStockLocation().get(location.parentId).then((var loc) {
|
InvenTreeStockLocation().get(context, location.parentId).then((var loc) {
|
||||||
if (loc is InvenTreeStockLocation) {
|
if (loc is InvenTreeStockLocation) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
||||||
}
|
}
|
||||||
@ -134,68 +126,63 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: Text(_title),
|
|
||||||
),
|
|
||||||
drawer: new InvenTreeDrawer(context),
|
|
||||||
body: ListView(
|
|
||||||
children: <Widget> [
|
|
||||||
locationDescriptionCard(),
|
|
||||||
ExpansionPanelList(
|
|
||||||
expansionCallback: (int index, bool isExpanded) {
|
|
||||||
setState(() {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
InvenTreePreferences().expandLocationList = !isExpanded;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
InvenTreePreferences().expandStockList = !isExpanded;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
return ListView(
|
||||||
children: <ExpansionPanel> [
|
children: <Widget> [
|
||||||
ExpansionPanel(
|
locationDescriptionCard(),
|
||||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
ExpansionPanelList(
|
||||||
return ListTile(
|
expansionCallback: (int index, bool isExpanded) {
|
||||||
title: Text("Sublocations"),
|
setState(() {
|
||||||
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
switch (index) {
|
||||||
trailing: Text("${_sublocations.length}"),
|
case 0:
|
||||||
onTap: () {
|
InvenTreePreferences().expandLocationList = !isExpanded;
|
||||||
setState(() {
|
break;
|
||||||
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
|
case 1:
|
||||||
});
|
InvenTreePreferences().expandStockList = !isExpanded;
|
||||||
},
|
break;
|
||||||
);
|
default:
|
||||||
},
|
break;
|
||||||
body: SublocationList(_sublocations),
|
}
|
||||||
isExpanded: InvenTreePreferences().expandLocationList && _sublocations.length > 0,
|
});
|
||||||
),
|
|
||||||
ExpansionPanel(
|
},
|
||||||
headerBuilder: (BuildContext context, bool isExpanded) {
|
children: <ExpansionPanel> [
|
||||||
return ListTile(
|
ExpansionPanel(
|
||||||
title: Text("Stock Items"),
|
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||||
leading: FaIcon(FontAwesomeIcons.boxes),
|
return ListTile(
|
||||||
trailing: Text("${_items.length}"),
|
title: Text("Sublocations"),
|
||||||
onTap: () {
|
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
||||||
setState(() {
|
trailing: Text("${_sublocations.length}"),
|
||||||
InvenTreePreferences().expandStockList = !InvenTreePreferences().expandStockList;
|
onTap: () {
|
||||||
});
|
setState(() {
|
||||||
},
|
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
|
||||||
);
|
});
|
||||||
},
|
},
|
||||||
body: StockList(_items),
|
);
|
||||||
isExpanded: InvenTreePreferences().expandStockList && _items.length > 0,
|
},
|
||||||
)
|
body: SublocationList(_sublocations),
|
||||||
]
|
isExpanded: InvenTreePreferences().expandLocationList && _sublocations.length > 0,
|
||||||
),
|
),
|
||||||
]
|
ExpansionPanel(
|
||||||
)
|
headerBuilder: (BuildContext context, bool isExpanded) {
|
||||||
|
return ListTile(
|
||||||
|
title: Text("Stock Items"),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.boxes),
|
||||||
|
trailing: Text("${_items.length}"),
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
InvenTreePreferences().expandStockList = !InvenTreePreferences().expandStockList;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
body: StockList(_items),
|
||||||
|
isExpanded: InvenTreePreferences().expandStockList && _items.length > 0,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -208,7 +195,7 @@ class SublocationList extends StatelessWidget {
|
|||||||
|
|
||||||
void _openLocation(BuildContext context, int pk) {
|
void _openLocation(BuildContext context, int pk) {
|
||||||
|
|
||||||
InvenTreeStockLocation().get(pk).then((var loc) {
|
InvenTreeStockLocation().get(context, pk).then((var loc) {
|
||||||
if (loc is InvenTreeStockLocation) {
|
if (loc is InvenTreeStockLocation) {
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
||||||
@ -244,7 +231,7 @@ class StockList extends StatelessWidget {
|
|||||||
StockList(this._items);
|
StockList(this._items);
|
||||||
|
|
||||||
void _openItem(BuildContext context, int pk) {
|
void _openItem(BuildContext context, int pk) {
|
||||||
InvenTreeStockItem().get(pk).then((var item) {
|
InvenTreeStockItem().get(context, pk).then((var item) {
|
||||||
if (item is InvenTreeStockItem) {
|
if (item is InvenTreeStockItem) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
|
|
||||||
@ -21,7 +22,10 @@ class PartDetailWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _PartDisplayState extends State<PartDetailWidget> {
|
class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle(BuildContext context) { return "Part"; }
|
||||||
|
|
||||||
_PartDisplayState(this.part) {
|
_PartDisplayState(this.part) {
|
||||||
// TODO
|
// TODO
|
||||||
@ -29,6 +33,11 @@ class _PartDisplayState extends State<PartDetailWidget> {
|
|||||||
|
|
||||||
InvenTreePart part;
|
InvenTreePart part;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> request(BuildContext context) async {
|
||||||
|
await part.reload(context);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Build a list of tiles to display under the part description
|
* Build a list of tiles to display under the part description
|
||||||
*/
|
*/
|
||||||
@ -62,7 +71,7 @@ class _PartDisplayState extends State<PartDetailWidget> {
|
|||||||
leading: FaIcon(FontAwesomeIcons.stream),
|
leading: FaIcon(FontAwesomeIcons.stream),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (part.categoryId > 0) {
|
if (part.categoryId > 0) {
|
||||||
InvenTreePartCategory().get(part.categoryId).then((var cat) {
|
InvenTreePartCategory().get(context, part.categoryId).then((var cat) {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) => CategoryDisplayWidget(cat)));
|
builder: (context) => CategoryDisplayWidget(cat)));
|
||||||
});
|
});
|
||||||
@ -154,22 +163,11 @@ class _PartDisplayState extends State<PartDetailWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return Scaffold(
|
return Center(
|
||||||
appBar: AppBar(
|
child: ListView(
|
||||||
title: Text("Part Details"),
|
children: partTiles(),
|
||||||
),
|
),
|
||||||
drawer: new InvenTreeDrawer(context),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
child: FaIcon(FontAwesomeIcons.ellipsisH),
|
|
||||||
// TODO - Add pop-up icons
|
|
||||||
// Ref: https://stackoverflow.com/questions/46480221/flutter-floating-action-button-with-speed-dial#46480722
|
|
||||||
),
|
|
||||||
body: Center(
|
|
||||||
child: ListView(
|
|
||||||
children: partTiles(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
66
lib/widget/refreshable_state.dart
Normal file
66
lib/widget/refreshable_state.dart
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
|
||||||
|
|
||||||
|
abstract class RefreshableState<T extends StatefulWidget> extends State<T> {
|
||||||
|
|
||||||
|
// Storage for context once "Build" is called
|
||||||
|
BuildContext context;
|
||||||
|
|
||||||
|
String getAppBarTitle(BuildContext context) { return "App Bar Title"; }
|
||||||
|
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => onBuild(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function called after the widget is first build
|
||||||
|
Future<void> onBuild(BuildContext context) async {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to request data for this page
|
||||||
|
Future<void> request(BuildContext context) async {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> refresh() async {
|
||||||
|
await request(context);
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to construct an appbar (override if needed)
|
||||||
|
AppBar getAppBar(BuildContext context) {
|
||||||
|
return AppBar(
|
||||||
|
title: Text(getAppBarTitle(context))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to construct a drawer (override if needed)
|
||||||
|
Widget getDrawer(BuildContext context) {
|
||||||
|
return InvenTreeDrawer(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to construct a body (MUST BE PROVIDED)
|
||||||
|
Widget getBody(BuildContext context);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
// Save the context for future use
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: getAppBar(context),
|
||||||
|
drawer: getDrawer(context),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: refresh,
|
||||||
|
child: getBody(context)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,12 +4,15 @@ import 'package:InvenTree/inventree/stock.dart';
|
|||||||
import 'package:InvenTree/inventree/part.dart';
|
import 'package:InvenTree/inventree/part.dart';
|
||||||
import 'package:InvenTree/widget/location_display.dart';
|
import 'package:InvenTree/widget/location_display.dart';
|
||||||
import 'package:InvenTree/widget/part_detail.dart';
|
import 'package:InvenTree/widget/part_detail.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:InvenTree/api.dart';
|
import 'package:InvenTree/api.dart';
|
||||||
|
|
||||||
import 'package:InvenTree/widget/drawer.dart';
|
import 'package:InvenTree/widget/drawer.dart';
|
||||||
|
import 'package:InvenTree/widget/refreshable_state.dart';
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||||
@ -25,7 +28,10 @@ class StockDetailWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class _StockItemDisplayState extends State<StockDetailWidget> {
|
class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getAppBarTitle(BuildContext context) { return "Stock Item"; }
|
||||||
|
|
||||||
final TextEditingController _quantityController = TextEditingController();
|
final TextEditingController _quantityController = TextEditingController();
|
||||||
final TextEditingController _notesController = TextEditingController();
|
final TextEditingController _notesController = TextEditingController();
|
||||||
@ -42,13 +48,9 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
|
|
||||||
final InvenTreeStockItem item;
|
final InvenTreeStockItem item;
|
||||||
|
|
||||||
/**
|
@override
|
||||||
* Function to reload the page data
|
Future<void> request(BuildContext context) async {
|
||||||
*/
|
await item.reload(context);
|
||||||
Future<void> _refresh() async {
|
|
||||||
|
|
||||||
await item.reload();
|
|
||||||
setState(() {});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _editStockItem() {
|
void _editStockItem() {
|
||||||
@ -92,7 +94,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
_notesController.clear();
|
_notesController.clear();
|
||||||
|
|
||||||
// TODO - Handle error cases
|
// TODO - Handle error cases
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _addStockDialog() async {
|
void _addStockDialog() async {
|
||||||
@ -158,7 +160,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
|
|
||||||
// TODO - Handle error cases
|
// TODO - Handle error cases
|
||||||
|
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _removeStockDialog() {
|
void _removeStockDialog() {
|
||||||
@ -227,7 +229,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
|
|
||||||
// TODO - Handle error cases
|
// TODO - Handle error cases
|
||||||
|
|
||||||
_refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _countStockDialog() async {
|
void _countStockDialog() async {
|
||||||
@ -322,7 +324,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
leading: FaIcon(FontAwesomeIcons.shapes),
|
leading: FaIcon(FontAwesomeIcons.shapes),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (item.partId > 0) {
|
if (item.partId > 0) {
|
||||||
InvenTreePart().get(item.partId).then((var part) {
|
InvenTreePart().get(context, item.partId).then((var part) {
|
||||||
if (part is InvenTreePart) {
|
if (part is InvenTreePart) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
}
|
}
|
||||||
@ -362,7 +364,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (item.locationId > 0) {
|
if (item.locationId > 0) {
|
||||||
InvenTreeStockLocation().get(item.locationId).then((var loc) {
|
InvenTreeStockLocation().get(context, item.locationId).then((var loc) {
|
||||||
Navigator.push(context, MaterialPageRoute(
|
Navigator.push(context, MaterialPageRoute(
|
||||||
builder: (context) => LocationDisplayWidget(loc)));
|
builder: (context) => LocationDisplayWidget(loc)));
|
||||||
});
|
});
|
||||||
@ -464,26 +466,30 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
|
|||||||
return buttons;
|
return buttons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget getBody(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: stockTiles()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
|
this.context = context;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: getAppBar(context),
|
||||||
title: Text("Stock Item"),
|
drawer: getDrawer(context),
|
||||||
),
|
|
||||||
drawer: new InvenTreeDrawer(context),
|
|
||||||
floatingActionButton: SpeedDial(
|
floatingActionButton: SpeedDial(
|
||||||
visible: true,
|
visible: true,
|
||||||
animatedIcon: AnimatedIcons.menu_close,
|
animatedIcon: AnimatedIcons.menu_close,
|
||||||
heroTag: 'stock-item-fab',
|
heroTag: 'stock-item-fab',
|
||||||
children: actionButtons(),
|
children: actionButtons(),
|
||||||
),
|
),
|
||||||
body: Center(
|
body: RefreshIndicator(
|
||||||
child: new RefreshIndicator(
|
onRefresh: refresh,
|
||||||
onRefresh: _refresh,
|
child: getBody(context)
|
||||||
child: ListView(
|
|
||||||
children: stockTiles(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user