mirror of
https://github.com/inventree/inventree-app.git
synced 2025-05-14 13:03:11 +00:00
Display overlay screen for blocking operations (#186)
* Catch state error in homepage widget * Add flutter_overlay_loader lib - Displays an overlay screen to indicate blocking operation * Wrap blocking widget transitions in a loading overlay - Prevents user from doing other things while loading - Shows the user that something is happening * Linting fixes * Show overlay when uploading attachment file * Show overlay when downloading file also * Show overlay when loading or submitting API forms - Major improvements to usability "feel" * UI improvements for stock item test results widget * Fix API_FORM bug - onSuccess function was not being called
This commit is contained in:
parent
277193ecb0
commit
01dd046dd1
@ -651,12 +651,6 @@ class InvenTreeAPI {
|
|||||||
*/
|
*/
|
||||||
Future<void> downloadFile(String url, {bool openOnDownload = true}) async {
|
Future<void> downloadFile(String url, {bool openOnDownload = true}) async {
|
||||||
|
|
||||||
showSnackIcon(
|
|
||||||
L10().downloading,
|
|
||||||
icon: FontAwesomeIcons.download,
|
|
||||||
success: true
|
|
||||||
);
|
|
||||||
|
|
||||||
// Find the local downlods directory
|
// Find the local downlods directory
|
||||||
final Directory dir = await getTemporaryDirectory();
|
final Directory dir = await getTemporaryDirectory();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import "package:inventree/widget/fields.dart";
|
|||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
|
|
||||||
|
|
||||||
@ -859,7 +860,9 @@ Future<void> launchApiForm(
|
|||||||
|
|
||||||
if (url.isNotEmpty) {
|
if (url.isNotEmpty) {
|
||||||
|
|
||||||
|
showLoadingOverlay(context);
|
||||||
var options = await InvenTreeAPI().options(url);
|
var options = await InvenTreeAPI().options(url);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
// Invalid response from server
|
// Invalid response from server
|
||||||
if (!options.isValid()) {
|
if (!options.isValid()) {
|
||||||
@ -902,7 +905,7 @@ Future<void> launchApiForm(
|
|||||||
field.definition = extractFieldDefinition(serverFields, field.lookupPath);
|
field.definition = extractFieldDefinition(serverFields, field.lookupPath);
|
||||||
|
|
||||||
// Skip fields with empty definitions
|
// Skip fields with empty definitions
|
||||||
if (field.definition.isEmpty) {
|
if (url.isNotEmpty && field.definition.isEmpty) {
|
||||||
print("Warning: Empty field definition for field '${fieldName}'");
|
print("Warning: Empty field definition for field '${fieldName}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -987,8 +990,6 @@ class _APIFormWidgetState extends State<APIFormWidget> {
|
|||||||
|
|
||||||
List<String> nonFieldErrors = [];
|
List<String> nonFieldErrors = [];
|
||||||
|
|
||||||
Function(Map<String, dynamic>)? onSuccess;
|
|
||||||
|
|
||||||
bool spacerRequired = false;
|
bool spacerRequired = false;
|
||||||
|
|
||||||
List<Widget> _buildForm() {
|
List<Widget> _buildForm() {
|
||||||
@ -1102,20 +1103,25 @@ class _APIFormWidgetState extends State<APIFormWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (widget.method == "POST") {
|
if (widget.method == "POST") {
|
||||||
|
|
||||||
|
showLoadingOverlay(context);
|
||||||
final response = await InvenTreeAPI().post(
|
final response = await InvenTreeAPI().post(
|
||||||
widget.url,
|
widget.url,
|
||||||
body: data,
|
body: data,
|
||||||
expectedStatusCode: null
|
expectedStatusCode: null
|
||||||
);
|
);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
showLoadingOverlay(context);
|
||||||
final response = await InvenTreeAPI().patch(
|
final response = await InvenTreeAPI().patch(
|
||||||
widget.url,
|
widget.url,
|
||||||
body: data,
|
body: data,
|
||||||
expectedStatusCode: null
|
expectedStatusCode: null
|
||||||
);
|
);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -1259,7 +1265,7 @@ class _APIFormWidgetState extends State<APIFormWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Run custom onSuccess function
|
// Run custom onSuccess function
|
||||||
var successFunc = onSuccess;
|
var successFunc = widget.onSuccess;
|
||||||
|
|
||||||
// An "empty" URL means we don't want to submit the form anywhere
|
// An "empty" URL means we don't want to submit the form anywhere
|
||||||
// Perhaps we just want to process the data?
|
// Perhaps we just want to process the data?
|
||||||
|
@ -708,9 +708,7 @@ class InvenTreeAttachment extends InvenTreeModel {
|
|||||||
* Download this attachment file
|
* Download this attachment file
|
||||||
*/
|
*/
|
||||||
Future<void> downloadAttachment() async {
|
Future<void> downloadAttachment() async {
|
||||||
|
|
||||||
await InvenTreeAPI().downloadFile(attachment);
|
await InvenTreeAPI().downloadFile(attachment);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -333,6 +333,12 @@
|
|||||||
"filterTemplateDetail": "Show template parts",
|
"filterTemplateDetail": "Show template parts",
|
||||||
"@filterTemplateDetail": {},
|
"@filterTemplateDetail": {},
|
||||||
|
|
||||||
|
"filterTrackable": "Trackable",
|
||||||
|
"@filterTrackable": {},
|
||||||
|
|
||||||
|
"filterTrackableDetail": "Show trackable parts",
|
||||||
|
"@filterTrackableDetail": {},
|
||||||
|
|
||||||
"filterVirtual": "Virtual",
|
"filterVirtual": "Virtual",
|
||||||
"@filterVirtual": {},
|
"@filterVirtual": {},
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
|||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/inventree/model.dart";
|
import "package:inventree/inventree/model.dart";
|
||||||
import "package:inventree/widget/fields.dart";
|
import "package:inventree/widget/fields.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
@ -51,8 +52,8 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
|||||||
icon: FaIcon(FontAwesomeIcons.plusCircle),
|
icon: FaIcon(FontAwesomeIcons.plusCircle),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
FilePickerDialog.pickFile(
|
FilePickerDialog.pickFile(
|
||||||
onPicked: (File file) {
|
onPicked: (File file) async {
|
||||||
upload(file);
|
await upload(context, file);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -63,9 +64,11 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
|||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> upload(File file) async {
|
Future<void> upload(BuildContext context, File file) async {
|
||||||
|
|
||||||
|
showLoadingOverlay(context);
|
||||||
final bool result = await widget.attachment.uploadAttachment(file, widget.referenceId);
|
final bool result = await widget.attachment.uploadAttachment(file, widget.referenceId);
|
||||||
|
hideLoadingOverlay();
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
showSnackIcon(L10().uploadSuccess, success: true);
|
showSnackIcon(L10().uploadSuccess, success: true);
|
||||||
@ -121,7 +124,9 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
|||||||
subtitle: Text(attachment.comment),
|
subtitle: Text(attachment.comment),
|
||||||
leading: FaIcon(attachment.icon, color: COLOR_CLICK),
|
leading: FaIcon(attachment.icon, color: COLOR_CLICK),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
showLoadingOverlay(context);
|
||||||
await attachment.downloadAttachment();
|
await attachment.downloadAttachment();
|
||||||
|
hideLoadingOverlay();
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import "package:inventree/inventree/part.dart";
|
|||||||
|
|
||||||
import "package:inventree/widget/paginator.dart";
|
import "package:inventree/widget/paginator.dart";
|
||||||
import "package:inventree/widget/part_detail.dart";
|
import "package:inventree/widget/part_detail.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
|
|
||||||
|
|
||||||
@ -125,11 +126,14 @@ class _PaginatedBomListState extends PaginatedSearchState<PaginatedBomList> {
|
|||||||
height: 40,
|
height: 40,
|
||||||
),
|
),
|
||||||
onTap: subPart == null ? null : () async {
|
onTap: subPart == null ? null : () async {
|
||||||
InvenTreePart().get(bomItem.subPartId).then((var part) {
|
|
||||||
if (part is InvenTreePart) {
|
showLoadingOverlay(context);
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
var part = await InvenTreePart().get(bomItem.subPartId);
|
||||||
}
|
hideLoadingOverlay();
|
||||||
});
|
|
||||||
|
if (part is InvenTreePart) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import "package:inventree/inventree/part.dart";
|
|||||||
|
|
||||||
import "package:inventree/widget/category_list.dart";
|
import "package:inventree/widget/category_list.dart";
|
||||||
import "package:inventree/widget/part_list.dart";
|
import "package:inventree/widget/part_list.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/part_detail.dart";
|
import "package:inventree/widget/part_detail.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
@ -125,16 +126,21 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
FontAwesomeIcons.levelUpAlt,
|
FontAwesomeIcons.levelUpAlt,
|
||||||
color: COLOR_CLICK,
|
color: COLOR_CLICK,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (category == null || ((category?.parentId ?? 0) < 0)) {
|
|
||||||
|
int parentId = category?.parentId ?? -1;
|
||||||
|
|
||||||
|
if (parentId < 0) {
|
||||||
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
|
|
||||||
InvenTreePartCategory().get(category?.parentId ?? -1).then((var cat) {
|
showLoadingOverlay(context);
|
||||||
if (cat is InvenTreePartCategory) {
|
var cat = await InvenTreePartCategory().get(parentId);
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
hideLoadingOverlay();
|
||||||
}
|
|
||||||
});
|
if (cat is InvenTreePartCategory) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => CategoryDisplayWidget(cat)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -54,11 +54,13 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
InvenTreeAPI().registerCallback(() {
|
InvenTreeAPI().registerCallback(() {
|
||||||
setState(() {
|
|
||||||
// Reload the widget
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
// Reload the widget
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index of bottom navigation bar
|
// Index of bottom navigation bar
|
||||||
@ -192,6 +194,11 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ignore if the widget is no longer active
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
final notifications = await InvenTreeNotification().list();
|
final notifications = await InvenTreeNotification().list();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -10,6 +10,7 @@ import "package:inventree/l10.dart";
|
|||||||
import "package:inventree/inventree/stock.dart";
|
import "package:inventree/inventree/stock.dart";
|
||||||
|
|
||||||
import "package:inventree/widget/location_list.dart";
|
import "package:inventree/widget/location_list.dart";
|
||||||
|
import "package:inventree/widget/progress.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/stock_detail.dart";
|
import "package:inventree/widget/stock_detail.dart";
|
||||||
@ -218,19 +219,21 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
title: Text(L10().parentLocation),
|
title: Text(L10().parentLocation),
|
||||||
subtitle: Text("${location!.parentPathString}"),
|
subtitle: Text("${location!.parentPathString}"),
|
||||||
leading: FaIcon(FontAwesomeIcons.levelUpAlt, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.levelUpAlt, color: COLOR_CLICK),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
|
|
||||||
int parent = location?.parentId ?? -1;
|
int parentId = location?.parentId ?? -1;
|
||||||
|
|
||||||
if (parent < 0) {
|
if (parentId < 0) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
InvenTreeStockLocation().get(parent).then((var loc) {
|
showLoadingOverlay(context);
|
||||||
if (loc is InvenTreeStockLocation) {
|
var loc = await InvenTreeStockLocation().get(parentId);
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
hideLoadingOverlay();
|
||||||
}
|
|
||||||
});
|
if (loc is InvenTreeStockLocation) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -384,8 +384,8 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
|
|||||||
PagedSliverList.separated(
|
PagedSliverList.separated(
|
||||||
pagingController: _pagingController,
|
pagingController: _pagingController,
|
||||||
builderDelegate: PagedChildBuilderDelegate<InvenTreeModel>(
|
builderDelegate: PagedChildBuilderDelegate<InvenTreeModel>(
|
||||||
itemBuilder: (context, item, index) {
|
itemBuilder: (ctx, item, index) {
|
||||||
return buildItem(context, item);
|
return buildItem(ctx, item);
|
||||||
},
|
},
|
||||||
noItemsFoundIndicatorBuilder: (context) {
|
noItemsFoundIndicatorBuilder: (context) {
|
||||||
return NoResultsWidget(noResultsText);
|
return NoResultsWidget(noResultsText);
|
||||||
@ -450,9 +450,11 @@ class NoResultsWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(L10().noResults),
|
title: Text(
|
||||||
subtitle: Text(description),
|
description,
|
||||||
leading: FaIcon(FontAwesomeIcons.exclamationCircle),
|
style: TextStyle(fontStyle: FontStyle.italic),
|
||||||
|
),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.exclamationCircle, color: COLOR_WARNING),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,15 +253,17 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
title: Text(L10().partCategory),
|
title: Text(L10().partCategory),
|
||||||
subtitle: Text("${part.categoryName}"),
|
subtitle: Text("${part.categoryName}"),
|
||||||
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (part.categoryId > 0) {
|
if (part.categoryId > 0) {
|
||||||
InvenTreePartCategory().get(part.categoryId).then((var cat) {
|
|
||||||
|
|
||||||
if (cat is InvenTreePartCategory) {
|
showLoadingOverlay(context);
|
||||||
Navigator.push(context, MaterialPageRoute(
|
var cat = await InvenTreePartCategory().get(part.categoryId);
|
||||||
builder: (context) => CategoryDisplayWidget(cat)));
|
hideLoadingOverlay();
|
||||||
}
|
|
||||||
});
|
if (cat is InvenTreePartCategory) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => CategoryDisplayWidget(cat)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -104,6 +104,10 @@ class _PaginatedPartListState extends PaginatedSearchState<PaginatedPartList> {
|
|||||||
"label": L10().filterTemplate,
|
"label": L10().filterTemplate,
|
||||||
"help_text": L10().filterTemplateDetail
|
"help_text": L10().filterTemplateDetail
|
||||||
},
|
},
|
||||||
|
"trackable": {
|
||||||
|
"label": L10().filterTrackable,
|
||||||
|
"help_text": L10().filterTrackableDetail,
|
||||||
|
},
|
||||||
"virtual": {
|
"virtual": {
|
||||||
"label": L10().filterVirtual,
|
"label": L10().filterVirtual,
|
||||||
"help_text": L10().filterVirtualDetail,
|
"help_text": L10().filterVirtualDetail,
|
||||||
@ -122,15 +126,6 @@ class _PaginatedPartListState extends PaginatedSearchState<PaginatedPartList> {
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openPart(BuildContext context, int pk) {
|
|
||||||
// Attempt to load the part information
|
|
||||||
InvenTreePart().get(pk).then((var part) {
|
|
||||||
if (part is InvenTreePart) {
|
|
||||||
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildItem(BuildContext context, InvenTreeModel model) {
|
Widget buildItem(BuildContext context, InvenTreeModel model) {
|
||||||
@ -147,7 +142,7 @@ class _PaginatedPartListState extends PaginatedSearchState<PaginatedPartList> {
|
|||||||
height: 40,
|
height: 40,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_openPart(context, part.pk);
|
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_overlay_loader/flutter_overlay_loader.dart";
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Construct a circular progress indicator
|
* Construct a circular progress indicator
|
||||||
@ -11,3 +12,16 @@ Widget progressIndicator() {
|
|||||||
child: CircularProgressIndicator()
|
child: CircularProgressIndicator()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void showLoadingOverlay(BuildContext context) {
|
||||||
|
Loader.show(
|
||||||
|
context,
|
||||||
|
themeData: Theme.of(context).copyWith(colorScheme: ColorScheme.fromSwatch())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void hideLoadingOverlay() {
|
||||||
|
Loader.hide();
|
||||||
|
}
|
||||||
|
@ -64,9 +64,12 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
|
|||||||
|
|
||||||
// Update current tab selection
|
// Update current tab selection
|
||||||
void onTabSelectionChanged(int index) {
|
void onTabSelectionChanged(int index) {
|
||||||
setState(() {
|
|
||||||
tabIndex = index;
|
if (mounted) {
|
||||||
});
|
setState(() {
|
||||||
|
tabIndex = index;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -87,6 +90,10 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
|
|||||||
|
|
||||||
Future<void> refresh(BuildContext context) async {
|
Future<void> refresh(BuildContext context) async {
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
loading = true;
|
loading = true;
|
||||||
});
|
});
|
||||||
|
@ -1,91 +0,0 @@
|
|||||||
import "package:inventree/inventree/part.dart";
|
|
||||||
import "package:inventree/widget/part_detail.dart";
|
|
||||||
import "package:inventree/widget/progress.dart";
|
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
|
||||||
import "package:flutter/material.dart";
|
|
||||||
|
|
||||||
import "package:inventree/l10.dart";
|
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
|
||||||
|
|
||||||
|
|
||||||
class StarredPartWidget extends StatefulWidget {
|
|
||||||
|
|
||||||
const StarredPartWidget({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_StarredPartState createState() => _StarredPartState();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class _StarredPartState extends RefreshableState<StarredPartWidget> {
|
|
||||||
|
|
||||||
List<InvenTreePart> starredParts = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
String getAppBarTitle(BuildContext context) => L10().partsStarred;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> request(BuildContext context) async {
|
|
||||||
|
|
||||||
final parts = await InvenTreePart().list(filters: {"starred": "true"});
|
|
||||||
|
|
||||||
starredParts.clear();
|
|
||||||
|
|
||||||
for (int idx = 0; idx < parts.length; idx++) {
|
|
||||||
if (parts[idx] is InvenTreePart) {
|
|
||||||
starredParts.add(parts[idx] as InvenTreePart);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _partResult(BuildContext context, int index) {
|
|
||||||
final part = starredParts[index];
|
|
||||||
|
|
||||||
return ListTile(
|
|
||||||
title: Text(part.fullname),
|
|
||||||
subtitle: Text(part.description),
|
|
||||||
leading: InvenTreeAPI().getImage(
|
|
||||||
part.thumbnail,
|
|
||||||
width: 40,
|
|
||||||
height: 40
|
|
||||||
),
|
|
||||||
onTap: () {
|
|
||||||
InvenTreePart().get(part.pk).then((var prt) {
|
|
||||||
if (prt is InvenTreePart) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => PartDetailWidget(prt))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget getBody(BuildContext context) {
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return progressIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (starredParts.isEmpty) {
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().partsNone),
|
|
||||||
subtitle: Text(L10().partsStarredNone)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ListView.separated(
|
|
||||||
itemCount: starredParts.length,
|
|
||||||
itemBuilder: _partResult,
|
|
||||||
separatorBuilder: (_, __) => const Divider(height: 3),
|
|
||||||
physics: ClampingScrollPhysics(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -475,13 +475,16 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
color: item.statusColor
|
color: item.statusColor
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (item.partId > 0) {
|
if (item.partId > 0) {
|
||||||
InvenTreePart().get(item.partId).then((var part) {
|
|
||||||
if (part is InvenTreePart) {
|
showLoadingOverlay(context);
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
var part = await InvenTreePart().get(item.partId);
|
||||||
}
|
hideLoadingOverlay();
|
||||||
});
|
|
||||||
|
if (part is InvenTreePart) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(builder: (context) => PartDetailWidget(part)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
//trailing: Text(item.serialOrQuantityDisplay()),
|
//trailing: Text(item.serialOrQuantityDisplay()),
|
||||||
@ -533,15 +536,17 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
FontAwesomeIcons.mapMarkerAlt,
|
FontAwesomeIcons.mapMarkerAlt,
|
||||||
color: COLOR_CLICK,
|
color: COLOR_CLICK,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () async {
|
||||||
if (item.locationId > 0) {
|
if (item.locationId > 0) {
|
||||||
InvenTreeStockLocation().get(item.locationId).then((var loc) {
|
|
||||||
|
|
||||||
if (loc is InvenTreeStockLocation) {
|
showLoadingOverlay(context);
|
||||||
Navigator.push(context, MaterialPageRoute(
|
var loc = await InvenTreeStockLocation().get(item.locationId);
|
||||||
builder: (context) => LocationDisplayWidget(loc)));
|
hideLoadingOverlay();
|
||||||
}
|
|
||||||
});
|
if (loc is InvenTreeStockLocation) {
|
||||||
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => LocationDisplayWidget(loc)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -156,6 +156,7 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
String _test = "";
|
String _test = "";
|
||||||
bool _result = false;
|
bool _result = false;
|
||||||
String _value = "";
|
String _value = "";
|
||||||
|
String _notes = "";
|
||||||
|
|
||||||
FaIcon _icon = FaIcon(FontAwesomeIcons.questionCircle, color: COLOR_BLUE);
|
FaIcon _icon = FaIcon(FontAwesomeIcons.questionCircle, color: COLOR_BLUE);
|
||||||
bool _valueRequired = false;
|
bool _valueRequired = false;
|
||||||
@ -168,11 +169,13 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
_value = item.latestResult()?.value ?? "";
|
_value = item.latestResult()?.value ?? "";
|
||||||
_valueRequired = item.requiresValue;
|
_valueRequired = item.requiresValue;
|
||||||
_attachmentRequired = item.requiresAttachment;
|
_attachmentRequired = item.requiresAttachment;
|
||||||
|
_notes = item.latestResult()?.notes ?? "";
|
||||||
} else if (item is InvenTreeStockItemTestResult) {
|
} else if (item is InvenTreeStockItemTestResult) {
|
||||||
_result = item.result;
|
_result = item.result;
|
||||||
_test = item.testName;
|
_test = item.testName;
|
||||||
_required = false;
|
_required = false;
|
||||||
_value = item.value;
|
_value = item.value;
|
||||||
|
_notes = item.notes;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_result == true) {
|
if (_result == true) {
|
||||||
@ -187,8 +190,9 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
|
|
||||||
tiles.add(ListTile(
|
tiles.add(ListTile(
|
||||||
title: Text(_test, style: TextStyle(fontWeight: _required ? FontWeight.bold : FontWeight.normal)),
|
title: Text(_test, style: TextStyle(fontWeight: _required ? FontWeight.bold : FontWeight.normal)),
|
||||||
subtitle: Text(_value),
|
subtitle: Text(_notes),
|
||||||
trailing: _icon,
|
trailing: Text(_value),
|
||||||
|
leading: _icon,
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
addTestResult(
|
addTestResult(
|
||||||
context,
|
context,
|
||||||
|
@ -104,14 +104,6 @@ class _PaginatedStockItemListState extends PaginatedSearchState<PaginatedStockIt
|
|||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _openItem(BuildContext context, int pk) {
|
|
||||||
InvenTreeStockItem().get(pk).then((var item) {
|
|
||||||
if (item is InvenTreeStockItem) {
|
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget buildItem(BuildContext context, InvenTreeModel model) {
|
Widget buildItem(BuildContext context, InvenTreeModel model) {
|
||||||
|
|
||||||
@ -132,7 +124,7 @@ class _PaginatedStockItemListState extends PaginatedSearchState<PaginatedStockIt
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_openItem(context, item.pk);
|
Navigator.push(context, MaterialPageRoute(builder: (context) => StockDetailWidget(item)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -291,6 +291,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.9+1"
|
version: "0.6.9+1"
|
||||||
|
flutter_overlay_loader:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_overlay_loader
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -22,12 +22,13 @@ dependencies:
|
|||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_markdown: ^0.6.9 # Rendering markdown
|
flutter_markdown: ^0.6.9 # Rendering markdown
|
||||||
|
flutter_overlay_loader: ^2.0.0 # Overlay screen support
|
||||||
font_awesome_flutter: ^9.1.0 # FontAwesome icon set
|
font_awesome_flutter: ^9.1.0 # FontAwesome icon set
|
||||||
http: ^0.13.4
|
http: ^0.13.4
|
||||||
image_picker: ^0.8.3 # Select or take photos
|
image_picker: ^0.8.3 # Select or take photos
|
||||||
infinite_scroll_pagination: ^3.1.0 # Let the server do all the work!
|
infinite_scroll_pagination: ^3.1.0 # Let the server do all the work!
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
one_context: ^1.1.0 # Dialogs without requiring context
|
one_context: ^1.1.1 # Dialogs without requiring context
|
||||||
open_file: ^3.2.1 # Open local files
|
open_file: ^3.2.1 # Open local files
|
||||||
package_info_plus: ^1.0.4 # App information introspection
|
package_info_plus: ^1.0.4 # App information introspection
|
||||||
path: ^1.8.0
|
path: ^1.8.0
|
||||||
|
Loading…
x
Reference in New Issue
Block a user