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

Merge branch 'barcodez'

This commit is contained in:
Oliver Walters 2020-04-16 19:25:56 +10:00
commit 54d6e1c6d3
15 changed files with 587 additions and 375 deletions

View File

@ -182,15 +182,22 @@ class InvenTreeAPI {
var response = await get("").timeout(Duration(seconds: 10)).catchError((error) {
if (error is SocketException) {
errorMessage = "Could not connect to server.";
print(errorMessage);
throw errorMessage;
print("Could not connect to server");
return null;
} else if (error is TimeoutException) {
print("Server timeout");
return null;
} else {
// Unknown error type, re-throw error
print("Unknown error: ${error.toString()}");
throw error;
}
});
if (response == null) {
return false;
}
if (response.statusCode != 200) {
print("Invalid status code: " + response.statusCode.toString());
return false;

View File

@ -1,3 +1,4 @@
import 'package:InvenTree/widget/dialogs.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.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/part.dart';
import 'package:InvenTree/api.dart';
import 'package:InvenTree/widget/location_display.dart';
import 'package:InvenTree/widget/part_detail.dart';
import 'package:InvenTree/widget/category_display.dart';
@ -12,59 +15,139 @@ import 'package:InvenTree/widget/stock_detail.dart';
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...
final data = json.decode(result);
showProgressDialog(context, "Querying Server", "Sending barcode data to server");
// Look for an 'InvenTree' style barcode
if ((data['tool'] ?? '').toString().toLowerCase() == 'inventree') {
_handleInvenTreeBarcode(context, data);
}
/*
* POST the scanned barcode data to the server.
* 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!
else {
showDialog(
context: context,
child: new SimpleDialog(
title: new Text("Unknown barcode"),
children: <Widget>[
Text("Data: $result"),
]
)
);
}
hideProgressDialog(context);
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...
InvenTreeStockLocation().get(pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
});
if (pk != null) {
InvenTreeStockLocation().get(context, pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
});
} else {
// TODO - Show an error here!
}
} else if (codeType == 'stockitem') {
InvenTreeStockItem().get(pk).then((var item) {
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
});
} else if (codeType == 'part') {
InvenTreePart().get(pk).then((var part) {
Navigator.push(context,
MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
});
} else if (data.containsKey('stockitem')) {
pk = data['stockitem']['pk'] as int ?? null;
if (pk != null) {
InvenTreeStockItem().get(context, pk).then((var item) {
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
});
} 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()),
)
],
)
);
}
}

View File

@ -5,6 +5,10 @@ import 'model.dart';
* The InvenTreeCompany class repreents the Company model in the InvenTree database.
*/
class InvenTreeCompany extends InvenTreeModel {
@override
String NAME = "Company";
@override
String URL = "company/";

View File

@ -1,4 +1,8 @@
import 'dart:async';
import 'package:InvenTree/api.dart';
import 'package:InvenTree/widget/dialogs.dart';
import 'package:flutter/cupertino.dart';
import 'dart:convert';
@ -15,6 +19,8 @@ class InvenTreeModel {
// Override the endpoint URL for each subclass
String URL = "";
String NAME = "Model";
// JSON data which defines this object
Map<String, dynamic> jsondata = {};
@ -83,9 +89,30 @@ class InvenTreeModel {
/*
* 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) {
print("Error retrieving data");
@ -100,7 +127,7 @@ class InvenTreeModel {
}
// 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 error catching
@ -122,7 +149,27 @@ class InvenTreeModel {
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) {
print("Error retrieving data");
@ -135,7 +182,7 @@ class InvenTreeModel {
}
// 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) {
filters = {};
@ -154,7 +201,28 @@ class InvenTreeModel {
// TODO - Add "timeout"
// 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
List<InvenTreeModel> results = new List<InvenTreeModel>();

View File

@ -9,6 +9,10 @@ import 'package:path/path.dart' as path;
import 'package:http/http.dart' as http;
class InvenTreePartCategory extends InvenTreeModel {
@override
String NAME = "PartCategory";
@override
String URL = "part/category/";
@ -61,6 +65,9 @@ class InvenTreePartCategory extends InvenTreeModel {
class InvenTreePart extends InvenTreeModel {
@override
String NAME = "Part";
@override
String URL = "part/";

View File

@ -6,6 +6,10 @@ import 'model.dart';
import 'package:InvenTree/api.dart';
class InvenTreeStockItem extends InvenTreeModel {
@override
String NAME = "StockItem";
@override
String URL = "stock/";
@ -208,6 +212,10 @@ class InvenTreeStockItem extends InvenTreeModel {
class InvenTreeStockLocation extends InvenTreeModel {
@override
String NAME = "StockLocation";
@override
String URL = "stock/location/";

View File

@ -22,6 +22,8 @@ import 'preferences.dart';
import 'package:InvenTree/inventree/part.dart';
void main() async {
// await PrefService.init(prefix: "inventree_");
@ -31,10 +33,10 @@ void main() async {
// Load login details
InvenTreePreferences().loadLoginDetails();
runApp(MyApp());
runApp(InvenTreeApp());
}
class MyApp extends StatelessWidget {
class InvenTreeApp extends StatelessWidget {
// This widget is the root of your application.
@override
@ -42,16 +44,8 @@ class MyApp extends StatelessWidget {
return MaterialApp(
title: 'InvenTree',
theme: ThemeData(
// This is the theme of your application.
//
// 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,
primarySwatch: Colors.lightBlue,
secondaryHeaderColor: Colors.blueGrey,
),
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 {
MyHomePage({Key key, this.title}) : super(key: key);
@ -168,22 +134,6 @@ class _MyHomePageState extends State<MyHomePage> {
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

View File

@ -5,6 +5,7 @@ import 'package:InvenTree/preferences.dart';
import 'package:InvenTree/widget/part_detail.dart';
import 'package:InvenTree/widget/drawer.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:flutter/cupertino.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) {
_requestData();
}
@override
String getAppBarTitle(BuildContext context) { return "Part Category"; }
_CategoryDisplayState(this.category) {}
// The local InvenTreePartCategory object
final InvenTreePartCategory category;
@ -39,24 +41,18 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
List<InvenTreePart> _parts = List<InvenTreePart>();
String get _titleString {
if (category == null) {
return "Part Categories";
} else {
return "Part Category - ${category.name}";
}
@override
Future<void> onBuild(BuildContext context) async {
refresh();
}
/*
* Request data from the server
*/
void _requestData() {
@override
Future<void> request(BuildContext context) async {
int pk = category?.pk ?? -1;
// 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();
for (var cat in cats) {
@ -70,7 +66,7 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
});
// 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();
for (var part in parts) {
@ -112,7 +108,7 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null)));
} else {
// 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) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
}
@ -127,74 +123,68 @@ class _CategoryDisplayState extends State<CategoryDisplayWidget> {
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_titleString),
),
drawer: new InvenTreeDrawer(context),
body: ListView(
children: <Widget>[
getCategoryDescriptionCard(),
ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
Widget getBody(BuildContext context) {
return ListView(
children: <Widget>[
getCategoryDescriptionCard(),
ExpansionPanelList(
expansionCallback: (int index, bool isExpanded) {
setState(() {
switch (index) {
case 0:
InvenTreePreferences().expandCategoryList = !isExpanded;
break;
case 1:
InvenTreePreferences().expandPartList = !isExpanded;
break;
default:
break;
}
});
switch (index) {
case 0:
InvenTreePreferences().expandCategoryList = !isExpanded;
break;
case 1:
InvenTreePreferences().expandPartList = !isExpanded;
break;
default:
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> [
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
},
);
},
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,
)
],
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,
)
],
),
]
);
}
}
@ -211,7 +201,7 @@ class SubcategoryList extends StatelessWidget {
void _openCategory(BuildContext context, int pk) {
// Attempt to load the sub-category.
InvenTreePartCategory().get(pk).then((var cat) {
InvenTreePartCategory().get(context, pk).then((var cat) {
if (cat is InvenTreePartCategory) {
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
@ -252,7 +242,7 @@ class PartList extends StatelessWidget {
void _openPart(BuildContext context, int pk) {
// Attempt to load the part information
InvenTreePart().get(pk).then((var part) {
InvenTreePart().get(context, pk).then((var part) {
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));

View File

@ -2,6 +2,7 @@
import 'package:InvenTree/api.dart';
import 'package:InvenTree/inventree/company.dart';
import 'package:InvenTree/widget/drawer.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
@ -18,10 +19,18 @@ class CompanyDetailWidget extends StatefulWidget {
}
class _CompanyDetailState extends State<CompanyDetailWidget> {
class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
final InvenTreeCompany company;
@override
String getAppBarTitle(BuildContext context) { return "Company"; }
@override
Future<void> request(BuildContext context) async {
await company.reload(context);
}
_CompanyDetailState(this.company) {
// TODO
}
@ -122,17 +131,11 @@ class _CompanyDetailState extends State<CompanyDetailWidget> {
}
@override
Widget build(BuildContext context) {
Widget getBody(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("${company.name}"),
),
drawer: new InvenTreeDrawer(context),
body: Center(
child: ListView(
children: _companyTiles(),
)
return Center(
child: ListView(
children: _companyTiles(),
)
);
}

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:InvenTree/api.dart';
import 'package:InvenTree/inventree/company.dart';
import 'package:InvenTree/widget/drawer.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
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 _filteredCompanies = new List<InvenTreeCompany>();
var _title = "Companies";
String _title = "Companies";
@override
String getAppBarTitle(BuildContext context) { return _title; }
Map<String, String> _filters = Map<String, String>();
_CompanyListState(this._title, this._filters) {
_requestData();
_CompanyListState(this._title, this._filters) {}
@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();
@ -85,7 +93,7 @@ class _CompanyListState extends State<CompanyListWidget> {
),
onTap: () {
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)));
});
}
@ -94,38 +102,24 @@ class _CompanyListState extends State<CompanyListWidget> {
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("$_title"),
actions: <Widget>[
IconButton(
icon: FaIcon(FontAwesomeIcons.plus),
tooltip: 'New',
onPressed: null,
)
],
Widget getBody(BuildContext context) {
return ListView(
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: 'Filter results',
),
onChanged: (String text) {
setState(() {
_filterResults(text);
});
},
),
drawer: new InvenTreeDrawer(context),
body: ListView(
children: <Widget>[
TextField(
decoration: InputDecoration(
hintText: 'Filter results',
),
onChanged: (String text) {
setState(() {
_filterResults(text);
});
},
),
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: _showCompany, itemCount: _filteredCompanies.length)
],
)
ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemBuilder: _showCompany, itemCount: _filteredCompanies.length)
],
);
}
}

41
lib/widget/dialogs.dart Normal file
View 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);
}

View File

@ -8,6 +8,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
class LocationDisplayWidget extends StatefulWidget {
LocationDisplayWidget(this.location, {Key key}) : super(key: key);
@ -20,15 +22,15 @@ class LocationDisplayWidget extends StatefulWidget {
_LocationDisplayState createState() => _LocationDisplayState(location);
}
class _LocationDisplayState extends State<LocationDisplayWidget> {
_LocationDisplayState(this.location) {
_requestData();
}
class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
final InvenTreeStockLocation location;
@override
String getAppBarTitle(BuildContext context) { return "Stock Location"; }
_LocationDisplayState(this.location) {}
List<InvenTreeStockLocation> _sublocations = List<InvenTreeStockLocation>();
String _locationFilter = '';
@ -44,28 +46,18 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
List<InvenTreeStockItem> _items = List<InvenTreeStockItem>();
String get _title {
if (location == null) {
return "Stock Locations";
} else {
return "Stock Location - ${location.name}";
}
@override
Future<void> onBuild(BuildContext context) async {
refresh();
}
/*
* Request data from the server.
* It will be displayed once loaded
*
* - List of sublocations under this one
* - List of stock items at this location
*/
void _requestData() {
@override
Future<void> request(BuildContext context) async {
int pk = location?.pk ?? -1;
// 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();
for (var loc in locs) {
@ -76,18 +68,18 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
setState(() {});
// Request a list of stock-items under this one
InvenTreeStockItem().list(filters: {"location": "$pk"}).then((var items) {
_items.clear();
// Request a list of stock-items under this one
InvenTreeStockItem().list(context, filters: {"location": "$pk"}).then((var items) {
_items.clear();
for (var item in items) {
if (item is InvenTreeStockItem) {
_items.add(item);
for (var item in items) {
if (item is InvenTreeStockItem) {
_items.add(item);
}
}
}
setState(() {});
});
setState(() {});
});
});
}
@ -119,7 +111,7 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
if (location.parentId < 0) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
} else {
InvenTreeStockLocation().get(location.parentId).then((var loc) {
InvenTreeStockLocation().get(context, location.parentId).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
}
@ -134,68 +126,63 @@ class _LocationDisplayState extends State<LocationDisplayWidget> {
}
@override
Widget build(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;
}
});
Widget getBody(BuildContext context) {
},
children: <ExpansionPanel> [
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text("Sublocations"),
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
trailing: Text("${_sublocations.length}"),
onTap: () {
setState(() {
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
});
},
);
},
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,
)
]
),
]
)
return 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;
}
});
},
children: <ExpansionPanel> [
ExpansionPanel(
headerBuilder: (BuildContext context, bool isExpanded) {
return ListTile(
title: Text("Sublocations"),
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
trailing: Text("${_sublocations.length}"),
onTap: () {
setState(() {
InvenTreePreferences().expandLocationList = !InvenTreePreferences().expandLocationList;
});
},
);
},
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) {
InvenTreeStockLocation().get(pk).then((var loc) {
InvenTreeStockLocation().get(context, pk).then((var loc) {
if (loc is InvenTreeStockLocation) {
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
@ -244,7 +231,7 @@ class StockList extends StatelessWidget {
StockList(this._items);
void _openItem(BuildContext context, int pk) {
InvenTreeStockItem().get(pk).then((var item) {
InvenTreeStockItem().get(context, pk).then((var item) {
if (item is InvenTreeStockItem) {
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
}

View File

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:InvenTree/api.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:InvenTree/widget/drawer.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) {
// TODO
@ -29,6 +33,11 @@ class _PartDisplayState extends State<PartDetailWidget> {
InvenTreePart part;
@override
Future<void> request(BuildContext context) async {
await part.reload(context);
}
/*
* Build a list of tiles to display under the part description
*/
@ -62,7 +71,7 @@ class _PartDisplayState extends State<PartDetailWidget> {
leading: FaIcon(FontAwesomeIcons.stream),
onTap: () {
if (part.categoryId > 0) {
InvenTreePartCategory().get(part.categoryId).then((var cat) {
InvenTreePartCategory().get(context, part.categoryId).then((var cat) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => CategoryDisplayWidget(cat)));
});
@ -154,22 +163,11 @@ class _PartDisplayState extends State<PartDetailWidget> {
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Part Details"),
Widget getBody(BuildContext context) {
return Center(
child: ListView(
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(),
),
)
);
}
}

View 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)
)
);
}
}

View File

@ -4,12 +4,15 @@ import 'package:InvenTree/inventree/stock.dart';
import 'package:InvenTree/inventree/part.dart';
import 'package:InvenTree/widget/location_display.dart';
import 'package:InvenTree/widget/part_detail.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:InvenTree/api.dart';
import 'package:InvenTree/widget/drawer.dart';
import 'package:InvenTree/widget/refreshable_state.dart';
import 'package:flutter/services.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.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 _notesController = TextEditingController();
@ -42,13 +48,9 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
final InvenTreeStockItem item;
/**
* Function to reload the page data
*/
Future<void> _refresh() async {
await item.reload();
setState(() {});
@override
Future<void> request(BuildContext context) async {
await item.reload(context);
}
void _editStockItem() {
@ -92,7 +94,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
_notesController.clear();
// TODO - Handle error cases
_refresh();
refresh();
}
void _addStockDialog() async {
@ -158,7 +160,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
// TODO - Handle error cases
_refresh();
refresh();
}
void _removeStockDialog() {
@ -227,7 +229,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
// TODO - Handle error cases
_refresh();
refresh();
}
void _countStockDialog() async {
@ -322,7 +324,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
leading: FaIcon(FontAwesomeIcons.shapes),
onTap: () {
if (item.partId > 0) {
InvenTreePart().get(item.partId).then((var part) {
InvenTreePart().get(context, item.partId).then((var part) {
if (part is InvenTreePart) {
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
}
@ -362,7 +364,7 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
leading: FaIcon(FontAwesomeIcons.mapMarkerAlt),
onTap: () {
if (item.locationId > 0) {
InvenTreeStockLocation().get(item.locationId).then((var loc) {
InvenTreeStockLocation().get(context, item.locationId).then((var loc) {
Navigator.push(context, MaterialPageRoute(
builder: (context) => LocationDisplayWidget(loc)));
});
@ -464,26 +466,30 @@ class _StockItemDisplayState extends State<StockDetailWidget> {
return buttons;
}
@override
Widget getBody(BuildContext context) {
return ListView(
children: stockTiles()
);
}
@override
Widget build(BuildContext context) {
this.context = context;
return Scaffold(
appBar: AppBar(
title: Text("Stock Item"),
),
drawer: new InvenTreeDrawer(context),
appBar: getAppBar(context),
drawer: getDrawer(context),
floatingActionButton: SpeedDial(
visible: true,
animatedIcon: AnimatedIcons.menu_close,
heroTag: 'stock-item-fab',
children: actionButtons(),
),
body: Center(
child: new RefreshIndicator(
onRefresh: _refresh,
child: ListView(
children: stockTiles(),
)
)
body: RefreshIndicator(
onRefresh: refresh,
child: getBody(context)
)
);
}