mirror of
https://github.com/inventree/inventree-app.git
synced 2025-04-28 05:26:47 +00:00
UX Overhaul (#300)
* Add "global actions" to title bar * Implement actions * Add "speed dial" action buttons * tweak global action icons * Refactor actions for "stock item" display * Refactor "part" detail * part category * SupplierPart * More updates * Add BottomAppBar * Add a global bottom app bar * Move "edit" buttons back to the app bar * tweaks * Updates to drawer navigation menu * home screen improvements * text tweaks * Fix appBarTitle for notifications widget * Update "tabs" for category display * Fix for attachment widget * Update tabs for purchaseorder view * Update part display * Cleanup * Add "BOM" tab to part detail widget * Paginated list search cleanup * Update release notes * Update old function * linting * linting * Tweaks to bottomappbar - Increase icon size slightly - Adjust "actions" icon
This commit is contained in:
parent
74176cdda8
commit
a8f87e2f5a
@ -1,9 +1,12 @@
|
|||||||
## InvenTree App Release Notes
|
## InvenTree App Release Notes
|
||||||
---
|
---
|
||||||
|
|
||||||
### 0.10.3 - April 2023
|
### 0.11.0 - April 2023
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- Adds globally accessible action button for "search"
|
||||||
|
- Adds globally accessible action button for "barcode scan"
|
||||||
|
- Implement context actions using floating actions buttons
|
||||||
- Support barcode scanning for purchase orders
|
- Support barcode scanning for purchase orders
|
||||||
|
|
||||||
### 0.10.2 - March 2023
|
### 0.10.2 - March 2023
|
||||||
|
@ -21,6 +21,6 @@
|
|||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>1.0</string>
|
<string>1.0</string>
|
||||||
<key>MinimumOSVersion</key>
|
<key>MinimumOSVersion</key>
|
||||||
<string>9.0</string>
|
<string>11.0</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
archiveVersion = 1;
|
archiveVersion = 1;
|
||||||
classes = {
|
classes = {
|
||||||
};
|
};
|
||||||
objectVersion = 50;
|
objectVersion = 54;
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
@ -236,6 +236,7 @@
|
|||||||
};
|
};
|
||||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@ -250,6 +251,7 @@
|
|||||||
};
|
};
|
||||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
isa = PBXShellScriptBuildPhase;
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
alwaysOutOfDate = 1;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
);
|
);
|
||||||
@ -276,17 +278,17 @@
|
|||||||
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
"${BUILT_PRODUCTS_DIR}/SDWebImage/SDWebImage.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
|
"${BUILT_PRODUCTS_DIR}/Sentry/Sentry.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
|
"${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/audioplayers/audioplayers.framework",
|
"${BUILT_PRODUCTS_DIR}/audioplayers_darwin/audioplayers_darwin.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/camera/camera.framework",
|
"${BUILT_PRODUCTS_DIR}/camera_avfoundation/camera_avfoundation.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
|
"${BUILT_PRODUCTS_DIR}/device_info_plus/device_info_plus.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
|
"${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/image_picker/image_picker.framework",
|
"${BUILT_PRODUCTS_DIR}/image_picker_ios/image_picker_ios.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/open_file/open_file.framework",
|
"${BUILT_PRODUCTS_DIR}/open_filex/open_filex.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
|
"${BUILT_PRODUCTS_DIR}/package_info_plus/package_info_plus.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/path_provider_ios/path_provider_ios.framework",
|
"${BUILT_PRODUCTS_DIR}/path_provider_foundation/path_provider_foundation.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework",
|
"${BUILT_PRODUCTS_DIR}/qr_code_scanner/qr_code_scanner.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
|
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/shared_preferences_ios/shared_preferences_ios.framework",
|
"${BUILT_PRODUCTS_DIR}/shared_preferences_foundation/shared_preferences_foundation.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
|
"${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework",
|
||||||
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
|
"${BUILT_PRODUCTS_DIR}/url_launcher_ios/url_launcher_ios.framework",
|
||||||
);
|
);
|
||||||
@ -299,17 +301,17 @@
|
|||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SDWebImage.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Sentry.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/audioplayers_darwin.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/camera_avfoundation.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/device_info_plus.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/image_picker_ios.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_file.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/open_filex.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/package_info_plus.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_ios.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider_foundation.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/qr_code_scanner.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_ios.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/shared_preferences_foundation.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework",
|
||||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
|
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/url_launcher_ios.framework",
|
||||||
);
|
);
|
||||||
@ -392,7 +394,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@ -415,7 +417,7 @@
|
|||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -476,7 +478,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@ -523,7 +525,7 @@
|
|||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
@ -546,7 +548,7 @@
|
|||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
@ -577,7 +579,7 @@
|
|||||||
"$(PROJECT_DIR)/Flutter",
|
"$(PROJECT_DIR)/Flutter",
|
||||||
);
|
);
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 11.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
|
@ -71,5 +71,9 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>https</string>
|
<string>https</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
|
<key>UIApplicationSupportsIndirectInputEvents</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import "dart:io";
|
import "dart:io";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
import "package:inventree/inventree/purchase_order.dart";
|
import "package:inventree/inventree/purchase_order.dart";
|
||||||
import "package:inventree/widget/purchase_order_detail.dart";
|
import "package:inventree/widget/purchase_order_detail.dart";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
import "package:qr_code_scanner/qr_code_scanner.dart";
|
import "package:qr_code_scanner/qr_code_scanner.dart";
|
||||||
|
|
||||||
import "package:inventree/app_colors.dart";
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
@ -805,17 +805,12 @@ Future<void> scanQrCode(BuildContext context) async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
SpeedDialChild customBarcodeAction(BuildContext context, RefreshableState state, String barcode, String model, int pk) {
|
||||||
* Construct a generic ListTile widget to link or un-link a custom barcode from a model.
|
|
||||||
*/
|
|
||||||
Widget customBarcodeActionTile(BuildContext context, RefreshableState state, String barcode, String model, int pk) {
|
|
||||||
|
|
||||||
if (barcode.isEmpty) {
|
if (barcode.isEmpty) {
|
||||||
return ListTile(
|
return SpeedDialChild(
|
||||||
title: Text(L10().barcodeAssign),
|
label: L10().barcodeAssign,
|
||||||
subtitle: Text(L10().barcodeAssignDetail),
|
child: Icon(Icons.barcode_reader),
|
||||||
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
|
|
||||||
trailing: Icon(Icons.qr_code_scanner),
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
var handler = UniqueBarcodeHandler((String barcode) {
|
var handler = UniqueBarcodeHandler((String barcode) {
|
||||||
InvenTreeAPI().linkBarcode({
|
InvenTreeAPI().linkBarcode({
|
||||||
@ -823,8 +818,8 @@ Widget customBarcodeActionTile(BuildContext context, RefreshableState state, Str
|
|||||||
"barcode": barcode,
|
"barcode": barcode,
|
||||||
}).then((bool result) {
|
}).then((bool result) {
|
||||||
showSnackIcon(
|
showSnackIcon(
|
||||||
result ? L10().barcodeAssigned : L10().barcodeNotAssigned,
|
result ? L10().barcodeAssigned : L10().barcodeNotAssigned,
|
||||||
success: result
|
success: result
|
||||||
);
|
);
|
||||||
|
|
||||||
state.refresh(context);
|
state.refresh(context);
|
||||||
@ -832,18 +827,18 @@ Widget customBarcodeActionTile(BuildContext context, RefreshableState state, Str
|
|||||||
});
|
});
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => InvenTreeQRView(handler)
|
builder: (context) => InvenTreeQRView(handler)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return ListTile(
|
return SpeedDialChild(
|
||||||
title: Text(L10().barcodeUnassign),
|
child: Icon(Icons.barcode_reader),
|
||||||
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
|
label: L10().barcodeUnassign,
|
||||||
onTap: () async {
|
onTap: () {
|
||||||
InvenTreeAPI().unlinkBarcode({
|
InvenTreeAPI().unlinkBarcode({
|
||||||
model: pk.toString()
|
model: pk.toString()
|
||||||
}).then((bool result) {
|
}).then((bool result) {
|
||||||
@ -854,7 +849,7 @@ Widget customBarcodeActionTile(BuildContext context, RefreshableState state, Str
|
|||||||
|
|
||||||
state.refresh(context);
|
state.refresh(context);
|
||||||
});
|
});
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
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";
|
||||||
|
import "package:package_info_plus/package_info_plus.dart";
|
||||||
|
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
import "package:inventree/settings/about.dart";
|
||||||
import "package:inventree/settings/app_settings.dart";
|
import "package:inventree/settings/app_settings.dart";
|
||||||
import "package:inventree/settings/home_settings.dart";
|
import "package:inventree/settings/home_settings.dart";
|
||||||
import "package:inventree/settings/login.dart";
|
import "package:inventree/settings/login.dart";
|
||||||
@ -22,6 +24,16 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
|
|||||||
|
|
||||||
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
final _scaffoldKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Load "About" widget
|
||||||
|
*/
|
||||||
|
Future<void> _about() async {
|
||||||
|
PackageInfo.fromPlatform().then((PackageInfo info) {
|
||||||
|
Navigator.push(context,
|
||||||
|
MaterialPageRoute(builder: (context) => InvenTreeAboutWidget(info)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -31,9 +43,7 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
|
|||||||
),
|
),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: ListTile.divideTiles(
|
children: [
|
||||||
context: context,
|
|
||||||
tiles: <Widget>[
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().server),
|
title: Text(L10().server),
|
||||||
subtitle: Text(L10().configureServer),
|
subtitle: Text(L10().configureServer),
|
||||||
@ -65,9 +75,14 @@ class _InvenTreeSettingsState extends State<InvenTreeSettingsWidget> {
|
|||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePartSettingsWidget()));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreePartSettingsWidget()));
|
||||||
}
|
}
|
||||||
|
),
|
||||||
|
Divider(),
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().about),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.circleInfo),
|
||||||
|
onTap: _about,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
).toList()
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
@ -43,33 +43,35 @@ class _AttachmentWidgetState extends RefreshableState<AttachmentWidget> {
|
|||||||
List<InvenTreeAttachment> attachments = [];
|
List<InvenTreeAttachment> attachments = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().attachments;
|
String getAppBarTitle() => L10().attachments;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
if (!widget.hasUploadPermission) return [];
|
||||||
|
|
||||||
List<Widget> actions = [];
|
return [
|
||||||
|
IconButton(
|
||||||
if (widget.hasUploadPermission) {
|
icon: FaIcon(FontAwesomeIcons.camera),
|
||||||
// File upload
|
onPressed: () async {
|
||||||
actions.add(
|
FilePickerDialog.pickImageFromCamera().then((File? file) {
|
||||||
IconButton(
|
upload(context, file);
|
||||||
icon: FaIcon(FontAwesomeIcons.circlePlus),
|
});
|
||||||
onPressed: () async {
|
}
|
||||||
FilePickerDialog.pickFile(
|
),
|
||||||
onPicked: (File file) async {
|
IconButton(
|
||||||
await upload(context, file);
|
icon: FaIcon(FontAwesomeIcons.fileArrowUp),
|
||||||
}
|
onPressed: () async {
|
||||||
);
|
FilePickerDialog.pickFileFromDevice().then((File? file) {
|
||||||
},
|
upload(context, file);
|
||||||
)
|
});
|
||||||
);
|
}
|
||||||
}
|
)
|
||||||
|
];
|
||||||
return actions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> upload(BuildContext context, File file) async {
|
Future<void> upload(BuildContext context, File? file) async {
|
||||||
|
|
||||||
|
if (file == null) return;
|
||||||
|
|
||||||
showLoadingOverlay(context);
|
showLoadingOverlay(context);
|
||||||
final bool result = await widget.attachment.uploadAttachment(file, widget.referenceId);
|
final bool result = await widget.attachment.uploadAttachment(file, widget.referenceId);
|
||||||
|
@ -37,7 +37,7 @@ class _BillOfMaterialsState extends RefreshableState<BillOfMaterialsWidget> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) {
|
String getAppBarTitle() {
|
||||||
if (widget.isParentComponent) {
|
if (widget.isParentComponent) {
|
||||||
return L10().billOfMaterials;
|
return L10().billOfMaterials;
|
||||||
} else {
|
} else {
|
||||||
@ -46,7 +46,7 @@ class _BillOfMaterialsState extends RefreshableState<BillOfMaterialsWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
@ -33,27 +33,56 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().partCategory;
|
String getAppBarTitle() => L10().partCategory;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if ((widget.category != null) && InvenTreeAPI().checkPermission("part_category", "change")) {
|
if (widget.category != null) {
|
||||||
|
if (api.checkPermission("part_category", "change")) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit_square),
|
||||||
|
tooltip: L10().editCategory,
|
||||||
|
onPressed: () {
|
||||||
|
_editCategoryDialog(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("part", "add")) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.shapes),
|
||||||
|
label: L10().partCreateDetail,
|
||||||
|
onTap: _newPart,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.checkPermission("part_category", "add")) {
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
SpeedDialChild(
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
child: FaIcon(FontAwesomeIcons.sitemap),
|
||||||
tooltip: L10().edit,
|
label: L10().categoryCreateDetail,
|
||||||
onPressed: () {
|
onTap: () {
|
||||||
_editCategoryDialog(context);
|
_newCategory(context);
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _editCategoryDialog(BuildContext context) {
|
void _editCategoryDialog(BuildContext context) {
|
||||||
@ -154,25 +183,20 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
List<Widget> getTabIcons(BuildContext context) {
|
||||||
return BottomNavigationBar(
|
|
||||||
currentIndex: tabIndex,
|
return [
|
||||||
onTap: onTabSelectionChanged,
|
Tab(text: L10().details),
|
||||||
items: <BottomNavigationBarItem>[
|
Tab(text: L10().parts),
|
||||||
BottomNavigationBarItem(
|
];
|
||||||
icon: FaIcon(FontAwesomeIcons.sitemap),
|
}
|
||||||
label: L10().details,
|
|
||||||
),
|
@override
|
||||||
BottomNavigationBarItem(
|
List<Widget> getTabs(BuildContext context) {
|
||||||
icon: FaIcon(FontAwesomeIcons.shapes),
|
return [
|
||||||
label: L10().parts,
|
Column(children: detailTiles()),
|
||||||
),
|
Column(children: partsTiles()),
|
||||||
BottomNavigationBarItem(
|
];
|
||||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
|
||||||
label: L10().actions
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the "details" panel
|
// Construct the "details" panel
|
||||||
@ -216,7 +240,6 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
getCategoryDescriptionCard(extra: false),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10().parts,
|
L10().parts,
|
||||||
@ -298,74 +321,4 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> actionTiles(BuildContext context) {
|
|
||||||
|
|
||||||
List<Widget> tiles = [
|
|
||||||
getCategoryDescriptionCard(extra: false),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (InvenTreeAPI().checkPermission("part", "add")) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().categoryCreate),
|
|
||||||
subtitle: Text(L10().categoryCreateDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
|
|
||||||
onTap: () async {
|
|
||||||
_newCategory(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (widget.category != null) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().partCreate),
|
|
||||||
subtitle: Text(L10().partCreateDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.shapes, color: COLOR_CLICK),
|
|
||||||
onTap: _newPart,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tiles.isEmpty) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(
|
|
||||||
L10().actionsNone
|
|
||||||
),
|
|
||||||
subtitle: Text(
|
|
||||||
L10().permissionAccountDenied,
|
|
||||||
),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.userXmark),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
int partCount = 0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget getBody(BuildContext context) {
|
|
||||||
|
|
||||||
switch (tabIndex) {
|
|
||||||
case 0:
|
|
||||||
return Column(
|
|
||||||
children: detailTiles()
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return Column(
|
|
||||||
children: partsTiles()
|
|
||||||
);
|
|
||||||
case 2:
|
|
||||||
return ListView(
|
|
||||||
children: actionTiles(context)
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return ListView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ class _PartCategoryListState extends RefreshableState<PartCategoryList> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -41,7 +41,7 @@ class _PartCategoryListState extends RefreshableState<PartCategoryList> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().partCategories;
|
String getAppBarTitle() => L10().partCategories;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
@ -40,34 +41,36 @@ class _CompanyDetailState extends RefreshableState<CompanyDetailWidget> {
|
|||||||
int attachmentCount = 0;
|
int attachmentCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().company;
|
String getAppBarTitle() => L10().company;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
actions.add(
|
if (api.checkPermission("purchase_order", "change") ||
|
||||||
IconButton(
|
api.checkPermission("sales_order", "change") ||
|
||||||
icon: FaIcon(FontAwesomeIcons.globe),
|
api.checkPermission("return_order", "change")) {
|
||||||
onPressed: () async {
|
actions.add(
|
||||||
widget.company.goToInvenTreePage();
|
IconButton(
|
||||||
},
|
icon: Icon(Icons.edit_square),
|
||||||
)
|
tooltip: L10().companyEdit,
|
||||||
);
|
onPressed: () {
|
||||||
|
editCompany(context);
|
||||||
actions.add(
|
}
|
||||||
IconButton(
|
)
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
);
|
||||||
tooltip: L10().edit,
|
}
|
||||||
onPressed: () {
|
|
||||||
editCompany(context);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -32,7 +32,7 @@ class _CompanyListWidgetState extends RefreshableState<CompanyListWidget> {
|
|||||||
_CompanyListWidgetState();
|
_CompanyListWidgetState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => widget.title;
|
String getAppBarTitle() => widget.title;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
@ -1,23 +1,17 @@
|
|||||||
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";
|
||||||
import "package:package_info_plus/package_info_plus.dart";
|
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
import "package:inventree/barcode.dart";
|
|
||||||
import "package:inventree/l10.dart";
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
import "package:inventree/settings/about.dart";
|
|
||||||
import "package:inventree/settings/settings.dart";
|
import "package:inventree/settings/settings.dart";
|
||||||
|
import "package:inventree/widget/category_display.dart";
|
||||||
import "package:inventree/widget/search.dart";
|
import "package:inventree/widget/notifications.dart";
|
||||||
|
import "package:inventree/widget/purchase_order_list.dart";
|
||||||
|
import "package:inventree/widget/location_display.dart";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Custom "drawer" widget for the InvenTree app.
|
* Custom "drawer" widget for the InvenTree app.
|
||||||
*
|
|
||||||
* - Provides a "home" button which completely unwinds the widget stack
|
|
||||||
* - Global search
|
|
||||||
* - Barcode scan
|
|
||||||
*/
|
*/
|
||||||
class InvenTreeDrawer extends StatelessWidget {
|
class InvenTreeDrawer extends StatelessWidget {
|
||||||
|
|
||||||
@ -42,89 +36,125 @@ class InvenTreeDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _search() {
|
// Load "parts" page
|
||||||
|
void _parts() {
|
||||||
|
_closeDrawer();
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => CategoryDisplayWidget(null))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!InvenTreeAPI().checkConnection()) return;
|
// Load "stock" page
|
||||||
|
void _stock() {
|
||||||
|
_closeDrawer();
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) => LocationDisplayWidget(null))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load "purchase orders" page
|
||||||
|
void _purchaseOrders() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
|
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => SearchWidget(true)
|
builder: (context) => PurchaseOrderListWidget(filters: {})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Load notifications screen
|
||||||
* Launch the camera to scan a QR code.
|
void _notifications() {
|
||||||
* Upon successful scan, data are passed off to be decoded.
|
|
||||||
*/
|
|
||||||
Future <void> _scan() async {
|
|
||||||
if (!InvenTreeAPI().checkConnection()) return;
|
|
||||||
|
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
scanQrCode(context);
|
Navigator.push(context, MaterialPageRoute(builder: (context) => NotificationWidget()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Load settings widget
|
||||||
* Load settings widget
|
|
||||||
*/
|
|
||||||
void _settings() {
|
void _settings() {
|
||||||
_closeDrawer();
|
_closeDrawer();
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget()));
|
Navigator.push(context, MaterialPageRoute(builder: (context) => InvenTreeSettingsWidget()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Construct list of tiles to display in the "drawer" menu
|
||||||
* Load "About" widget
|
List<Widget> drawerTiles(BuildContext context) {
|
||||||
*/
|
List<Widget> tiles = [];
|
||||||
Future<void> _about() async {
|
|
||||||
_closeDrawer();
|
|
||||||
|
|
||||||
PackageInfo.fromPlatform().then((PackageInfo info) {
|
// "Home" access
|
||||||
Navigator.push(context,
|
tiles.add(ListTile(
|
||||||
MaterialPageRoute(builder: (context) => InvenTreeAboutWidget(info)));
|
leading: FaIcon(FontAwesomeIcons.house),
|
||||||
});
|
title: Text(
|
||||||
|
L10().appTitle,
|
||||||
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
|
),
|
||||||
|
onTap: _home,
|
||||||
|
));
|
||||||
|
|
||||||
|
tiles.add(Divider());
|
||||||
|
|
||||||
|
if (InvenTreeAPI().checkPermission("part_category", "view")) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().parts),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.shapes),
|
||||||
|
onTap: _parts,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InvenTreeAPI().checkPermission("stock_location", "view")) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().stock),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.boxesStacked),
|
||||||
|
onTap: _stock,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InvenTreeAPI().checkPermission("purchase_order", "view")) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().purchaseOrders),
|
||||||
|
leading: FaIcon(FontAwesomeIcons.cartShopping),
|
||||||
|
onTap: _purchaseOrders,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tiles.length > 2) {
|
||||||
|
tiles.add(Divider());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (InvenTreeAPI().supportsNotifications) {
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
leading: FaIcon(FontAwesomeIcons.bell),
|
||||||
|
title: Text(L10().notifications),
|
||||||
|
onTap: _notifications,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
tiles.add(
|
||||||
|
ListTile(
|
||||||
|
title: Text(L10().settings),
|
||||||
|
leading: Icon(Icons.settings),
|
||||||
|
onTap: _settings,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
return Drawer(
|
return Drawer(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: ListTile.divideTiles(
|
children: drawerTiles(context),
|
||||||
context: context,
|
|
||||||
tiles: <Widget>[
|
|
||||||
ListTile(
|
|
||||||
leading: FaIcon(FontAwesomeIcons.house),
|
|
||||||
title: Text(
|
|
||||||
L10().appTitle,
|
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
onTap: _home,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().scanBarcode),
|
|
||||||
onTap: _scan,
|
|
||||||
leading: Icon(Icons.qr_code_scanner),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().search),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.magnifyingGlass),
|
|
||||||
onTap: _search,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().settings),
|
|
||||||
leading: Icon(Icons.settings),
|
|
||||||
onTap: _settings,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().about),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.circleInfo),
|
|
||||||
onTap: _about,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
).toList(),
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -13,15 +13,12 @@ import "package:inventree/settings/login.dart";
|
|||||||
import "package:inventree/settings/settings.dart";
|
import "package:inventree/settings/settings.dart";
|
||||||
import "package:inventree/user_profile.dart";
|
import "package:inventree/user_profile.dart";
|
||||||
|
|
||||||
import "package:inventree/inventree/notification.dart";
|
|
||||||
|
|
||||||
import "package:inventree/widget/category_display.dart";
|
import "package:inventree/widget/category_display.dart";
|
||||||
import "package:inventree/widget/drawer.dart";
|
import "package:inventree/widget/drawer.dart";
|
||||||
import "package:inventree/widget/location_display.dart";
|
import "package:inventree/widget/location_display.dart";
|
||||||
import "package:inventree/widget/notifications.dart";
|
|
||||||
import "package:inventree/widget/part_list.dart";
|
import "package:inventree/widget/part_list.dart";
|
||||||
import "package:inventree/widget/purchase_order_list.dart";
|
import "package:inventree/widget/purchase_order_list.dart";
|
||||||
import "package:inventree/widget/search.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/spinner.dart";
|
import "package:inventree/widget/spinner.dart";
|
||||||
import "package:inventree/widget/company_list.dart";
|
import "package:inventree/widget/company_list.dart";
|
||||||
@ -35,7 +32,8 @@ class InvenTreeHomePage extends StatefulWidget {
|
|||||||
_InvenTreeHomePageState createState() => _InvenTreeHomePageState();
|
_InvenTreeHomePageState createState() => _InvenTreeHomePageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|
||||||
|
class _InvenTreeHomePageState extends State<InvenTreeHomePage> with BaseWidgetProperties {
|
||||||
|
|
||||||
_InvenTreeHomePageState() : super() {
|
_InvenTreeHomePageState() : super() {
|
||||||
// Load display settings
|
// Load display settings
|
||||||
@ -64,11 +62,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Index of bottom navigation bar
|
final homeKey = GlobalKey<ScaffoldState>();
|
||||||
int _tabIndex = 0;
|
|
||||||
|
|
||||||
// Number of outstanding notifications
|
|
||||||
int _notificationCounter = 0;
|
|
||||||
|
|
||||||
bool homeShowPo = false;
|
bool homeShowPo = false;
|
||||||
bool homeShowSubscribed = false;
|
bool homeShowSubscribed = false;
|
||||||
@ -76,8 +70,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
bool homeShowCustomers = false;
|
bool homeShowCustomers = false;
|
||||||
bool homeShowSuppliers = false;
|
bool homeShowSuppliers = false;
|
||||||
|
|
||||||
final GlobalKey<_InvenTreeHomePageState> _homeKey = GlobalKey<_InvenTreeHomePageState>();
|
|
||||||
|
|
||||||
// Selected user profile
|
// Selected user profile
|
||||||
UserProfile? _profile;
|
UserProfile? _profile;
|
||||||
|
|
||||||
@ -202,10 +194,10 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final notifications = await InvenTreeNotification().list();
|
// final notifications = await InvenTreeNotification().list();
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_notificationCounter = notifications.length;
|
// _notificationCounter = notifications.length;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -416,78 +408,17 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
* Return the main body widget for display.
|
* Return the main body widget for display.
|
||||||
* This depends on the current value of _tabIndex
|
* This depends on the current value of _tabIndex
|
||||||
*/
|
*/
|
||||||
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
|
||||||
if (!InvenTreeAPI().isConnected()) {
|
if (!InvenTreeAPI().isConnected()) {
|
||||||
return _connectionStatusWidget(context);
|
return _connectionStatusWidget(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (_tabIndex) {
|
return ListView(
|
||||||
case 1: // Search widget
|
scrollDirection: Axis.vertical,
|
||||||
return SearchWidget(false);
|
children: getListTiles(context),
|
||||||
case 2: // Notification widget
|
);
|
||||||
return NotificationWidget();
|
|
||||||
case 0: // Home widget
|
|
||||||
default:
|
|
||||||
return ListView(
|
|
||||||
scrollDirection: Axis.vertical,
|
|
||||||
children: getListTiles(context),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Construct the bottom navigation bar
|
|
||||||
*/
|
|
||||||
List<BottomNavigationBarItem> getNavBarItems(BuildContext context) {
|
|
||||||
|
|
||||||
List<BottomNavigationBarItem> items = <BottomNavigationBarItem>[
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.house),
|
|
||||||
label: L10().home,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.magnifyingGlass),
|
|
||||||
label: L10().search,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (InvenTreeAPI().supportsNotifications) {
|
|
||||||
items.add(
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: _notificationCounter == 0 ? FaIcon(FontAwesomeIcons.bell) : Stack(
|
|
||||||
children: <Widget>[
|
|
||||||
FaIcon(FontAwesomeIcons.bell),
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
child: Container(
|
|
||||||
padding: EdgeInsets.all(2),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.red,
|
|
||||||
borderRadius: BorderRadius.circular(20),
|
|
||||||
),
|
|
||||||
constraints: BoxConstraints(
|
|
||||||
minWidth: 12,
|
|
||||||
minHeight: 12,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
"${_notificationCounter}",
|
|
||||||
style: TextStyle(
|
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 9,
|
|
||||||
),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
label: L10().notifications,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return items;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -497,7 +428,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
var connecting = !connected && InvenTreeAPI().isConnecting();
|
var connecting = !connected && InvenTreeAPI().isConnecting();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
key: _homeKey,
|
key: homeKey,
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(L10().appTitle),
|
title: Text(L10().appTitle),
|
||||||
actions: [
|
actions: [
|
||||||
@ -512,17 +443,7 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> {
|
|||||||
),
|
),
|
||||||
drawer: InvenTreeDrawer(context),
|
drawer: InvenTreeDrawer(context),
|
||||||
body: getBody(context),
|
body: getBody(context),
|
||||||
bottomNavigationBar: connected ? BottomNavigationBar(
|
bottomNavigationBar: InvenTreeAPI().isConnected() ? buildBottomAppBar(context, homeKey) : null,
|
||||||
currentIndex: _tabIndex,
|
|
||||||
onTap: (int index) {
|
|
||||||
setState(() {
|
|
||||||
_tabIndex = index;
|
|
||||||
});
|
|
||||||
|
|
||||||
_refreshNotifications();
|
|
||||||
},
|
|
||||||
items: getNavBarItems(context),
|
|
||||||
) : null,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
|
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
@ -40,35 +41,96 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) { return L10().stockLocation; }
|
String getAppBarTitle() {
|
||||||
|
return L10().stockLocation;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if (location != null) {
|
// Add "locate" button
|
||||||
|
if (location != null && api.supportsMixin("locate")) {
|
||||||
// Add "locate" button
|
actions.add(
|
||||||
if (api.supportsMixin("locate")) {
|
|
||||||
actions.add(
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.magnifyingGlassLocation),
|
icon: Icon(Icons.travel_explore),
|
||||||
tooltip: L10().locateLocation,
|
tooltip: L10().locateLocation,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_locateStockLocation(context);
|
api.locateItemOrLocation(context, location: location!.pk);
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add "edit" button
|
||||||
|
if (location != null && api.checkPermission("stock_location", "change")) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit_square),
|
||||||
|
tooltip: L10().editLocation,
|
||||||
|
onPressed: () {
|
||||||
|
_editLocationDialog(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (location != null) {
|
||||||
|
// Scan items into this location
|
||||||
|
if (api.checkPermission("stock", "change")) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.qrcode),
|
||||||
|
label: L10().barcodeScanItem,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) =>
|
||||||
|
InvenTreeQRView(
|
||||||
|
StockLocationScanInItemsHandler(location!)))
|
||||||
|
).then((value) {
|
||||||
|
refresh(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add "edit" button
|
// Scan this location into another one
|
||||||
if (api.checkPermission("stock_location", "change")) {
|
if (api.checkPermission("stock_location", "change")) {
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
SpeedDialChild(
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
child: FaIcon(FontAwesomeIcons.qrcode),
|
||||||
tooltip: L10().edit,
|
label: L10().transferStockLocation,
|
||||||
onPressed: () { _editLocationDialog(context); },
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) =>
|
||||||
|
InvenTreeQRView(
|
||||||
|
ScanParentLocationHandler(location!)))
|
||||||
|
).then((value) {
|
||||||
|
refresh(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign or un-assign barcodes
|
||||||
|
if (api.supportModernBarcodes) {
|
||||||
|
actions.add(
|
||||||
|
customBarcodeAction(
|
||||||
|
context, this,
|
||||||
|
location!.customBarcode, "stocklocation",
|
||||||
|
location!.pk
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -77,23 +139,43 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
@override
|
||||||
* Request identification of this location
|
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||||
*/
|
List<SpeedDialChild> actions = [];
|
||||||
Future<void> _locateStockLocation(BuildContext context) async {
|
|
||||||
|
|
||||||
final _loc = location;
|
// Create new location
|
||||||
|
if (api.checkPermission("stock_location", "add")) {
|
||||||
if (_loc != null) {
|
actions.add(
|
||||||
api.locateItemOrLocation(context, location: _loc.pk);
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.sitemap),
|
||||||
|
label: L10().locationCreate,
|
||||||
|
onTap: () async {
|
||||||
|
_newLocation(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create new item
|
||||||
|
if (location != null && api.checkPermission("stock", "add")) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.boxesStacked),
|
||||||
|
label: L10().stockItemCreate,
|
||||||
|
onTap: () async {
|
||||||
|
_newStockItem(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Launch a dialog form to edit this stock location
|
* Launch a dialog form to edit this stock location
|
||||||
*/
|
*/
|
||||||
void _editLocationDialog(BuildContext context) {
|
void _editLocationDialog(BuildContext context) {
|
||||||
|
|
||||||
final _loc = location;
|
final _loc = location;
|
||||||
|
|
||||||
if (_loc == null) {
|
if (_loc == null) {
|
||||||
@ -101,12 +183,12 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_loc.editForm(
|
_loc.editForm(
|
||||||
context,
|
context,
|
||||||
L10().editLocation,
|
L10().editLocation,
|
||||||
onSuccess: (data) async {
|
onSuccess: (data) async {
|
||||||
refresh(context);
|
refresh(context);
|
||||||
showSnackIcon(L10().locationUpdated, success: true);
|
showSnackIcon(L10().locationUpdated, success: true);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +199,6 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> request(BuildContext context) async {
|
Future<void> request(BuildContext context) async {
|
||||||
|
|
||||||
// Reload location information
|
// Reload location information
|
||||||
if (location != null) {
|
if (location != null) {
|
||||||
final bool result = await location!.reload();
|
final bool result = await location!.reload();
|
||||||
@ -133,35 +214,32 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _newLocation(BuildContext context) async {
|
Future<void> _newLocation(BuildContext context) async {
|
||||||
|
|
||||||
int pk = location?.pk ?? -1;
|
int pk = location?.pk ?? -1;
|
||||||
|
|
||||||
InvenTreeStockLocation().createForm(
|
InvenTreeStockLocation().createForm(
|
||||||
context,
|
context,
|
||||||
L10().locationCreate,
|
L10().locationCreate,
|
||||||
data: {
|
data: {
|
||||||
"parent": (pk > 0) ? pk : null,
|
"parent": (pk > 0) ? pk : null,
|
||||||
},
|
},
|
||||||
onSuccess: (result) async {
|
onSuccess: (result) async {
|
||||||
|
Map<String, dynamic> data = result as Map<String, dynamic>;
|
||||||
|
|
||||||
Map<String, dynamic> data = result as Map<String, dynamic>;
|
if (data.containsKey("pk")) {
|
||||||
|
var loc = InvenTreeStockLocation.fromJson(data);
|
||||||
|
|
||||||
if (data.containsKey("pk")) {
|
Navigator.push(
|
||||||
var loc = InvenTreeStockLocation.fromJson(data);
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
Navigator.push(
|
builder: (context) => LocationDisplayWidget(loc)
|
||||||
context,
|
)
|
||||||
MaterialPageRoute(
|
);
|
||||||
builder: (context) => LocationDisplayWidget(loc)
|
}
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _newStockItem(BuildContext context) async {
|
Future<void> _newStockItem(BuildContext context) async {
|
||||||
|
|
||||||
int pk = location?.pk ?? -1;
|
int pk = location?.pk ?? -1;
|
||||||
|
|
||||||
if (location != null && pk <= 0) {
|
if (location != null && pk <= 0) {
|
||||||
@ -169,48 +247,46 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
InvenTreeStockItem().createForm(
|
InvenTreeStockItem().createForm(
|
||||||
context,
|
context,
|
||||||
L10().stockItemCreate,
|
L10().stockItemCreate,
|
||||||
data: {
|
data: {
|
||||||
"location": location != null ? pk : null,
|
"location": location != null ? pk : null,
|
||||||
},
|
},
|
||||||
onSuccess: (result) async {
|
onSuccess: (result) async {
|
||||||
|
Map<String, dynamic> data = result as Map<String, dynamic>;
|
||||||
|
|
||||||
Map<String, dynamic> data = result as Map<String, dynamic>;
|
if (data.containsKey("pk")) {
|
||||||
|
var item = InvenTreeStockItem.fromJson(data);
|
||||||
|
|
||||||
if (data.containsKey("pk")) {
|
Navigator.push(
|
||||||
var item = InvenTreeStockItem.fromJson(data);
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
Navigator.push(
|
builder: (context) => StockDetailWidget(item)
|
||||||
context,
|
)
|
||||||
MaterialPageRoute(
|
);
|
||||||
builder: (context) => StockDetailWidget(item)
|
}
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget locationDescriptionCard({bool includeActions = true}) {
|
Widget locationDescriptionCard({bool includeActions = true}) {
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
return Card(
|
return Card(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10().stockTopLevel,
|
L10().stockTopLevel,
|
||||||
style: TextStyle(fontStyle: FontStyle.italic)
|
style: TextStyle(fontStyle: FontStyle.italic)
|
||||||
),
|
),
|
||||||
leading: FaIcon(FontAwesomeIcons.boxesStacked),
|
leading: FaIcon(FontAwesomeIcons.boxesStacked),
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
List<Widget> children = [
|
List<Widget> children = [
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text("${location!.name}"),
|
title: Text("${location!.name}"),
|
||||||
subtitle: Text("${location!.description}"),
|
subtitle: Text("${location!.description}"),
|
||||||
leading: location!.customIcon ?? FaIcon(FontAwesomeIcons.boxesStacked),
|
leading: location!.customIcon ??
|
||||||
|
FaIcon(FontAwesomeIcons.boxesStacked),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -221,19 +297,19 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
subtitle: Text("${location!.parentPathString}"),
|
subtitle: Text("${location!.parentPathString}"),
|
||||||
leading: FaIcon(FontAwesomeIcons.turnUp, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.turnUp, color: COLOR_CLICK),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
||||||
int parentId = location?.parentId ?? -1;
|
int parentId = location?.parentId ?? -1;
|
||||||
|
|
||||||
if (parentId < 0) {
|
if (parentId < 0) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(null)));
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => LocationDisplayWidget(null)));
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
showLoadingOverlay(context);
|
showLoadingOverlay(context);
|
||||||
var loc = await InvenTreeStockLocation().get(parentId);
|
var loc = await InvenTreeStockLocation().get(parentId);
|
||||||
hideLoadingOverlay();
|
hideLoadingOverlay();
|
||||||
|
|
||||||
if (loc is InvenTreeStockLocation) {
|
if (loc is InvenTreeStockLocation) {
|
||||||
Navigator.push(context, MaterialPageRoute(builder: (context) => LocationDisplayWidget(loc)));
|
Navigator.push(context, MaterialPageRoute(
|
||||||
|
builder: (context) => LocationDisplayWidget(loc)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -242,63 +318,27 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: children,
|
children: children,
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
List<Widget> getTabIcons(BuildContext context) {
|
||||||
return BottomNavigationBar(
|
return [
|
||||||
currentIndex: tabIndex,
|
Tab(text: L10().details),
|
||||||
onTap: onTabSelectionChanged,
|
Tab(text: L10().stockItems),
|
||||||
items: <BottomNavigationBarItem> [
|
];
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.sitemap),
|
|
||||||
label: L10().details,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.boxesStacked),
|
|
||||||
label: L10().stock,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
|
||||||
label: L10().actions,
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
int stockItemCount = 0;
|
|
||||||
|
|
||||||
Widget getSelectedWidget(int index) {
|
|
||||||
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return Column(
|
|
||||||
children: detailTiles(),
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return Column(
|
|
||||||
children: stockTiles(),
|
|
||||||
);
|
|
||||||
case 2:
|
|
||||||
return ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: actionTiles()
|
|
||||||
).toList()
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return ListView();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
List<Widget> getTabs(BuildContext context) {
|
||||||
return getSelectedWidget(tabIndex);
|
return [
|
||||||
|
Column(children: detailTiles()),
|
||||||
|
Column(children: stockTiles()),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the "details" panel
|
// Construct the "details" panel
|
||||||
@ -306,18 +346,18 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
List<Widget> tiles = [
|
List<Widget> tiles = [
|
||||||
locationDescriptionCard(),
|
locationDescriptionCard(),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10().sublocations,
|
L10().sublocations,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
trailing: GestureDetector(
|
trailing: GestureDetector(
|
||||||
child: FaIcon(FontAwesomeIcons.filter),
|
child: FaIcon(FontAwesomeIcons.filter),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
setState(() {
|
setState(() {
|
||||||
showFilterOptions = !showFilterOptions;
|
showFilterOptions = !showFilterOptions;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: PaginatedStockLocationList(
|
child: PaginatedStockLocationList(
|
||||||
@ -335,13 +375,11 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
|
|
||||||
// Construct the "stock" panel
|
// Construct the "stock" panel
|
||||||
List<Widget> stockTiles() {
|
List<Widget> stockTiles() {
|
||||||
|
|
||||||
Map<String, String> filters = {
|
Map<String, String> filters = {
|
||||||
"location": location?.pk.toString() ?? "null",
|
"location": location?.pk.toString() ?? "null",
|
||||||
};
|
};
|
||||||
|
|
||||||
return [
|
return [
|
||||||
locationDescriptionCard(includeActions: false),
|
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(
|
title: Text(
|
||||||
L10().stock,
|
L10().stock,
|
||||||
@ -365,115 +403,4 @@ class _LocationDisplayState extends RefreshableState<LocationDisplayWidget> {
|
|||||||
)
|
)
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> actionTiles() {
|
|
||||||
List<Widget> tiles = [];
|
|
||||||
|
|
||||||
tiles.add(locationDescriptionCard(includeActions: false));
|
|
||||||
|
|
||||||
if (api.checkPermission("stock", "add")) {
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().locationCreate),
|
|
||||||
subtitle: Text(L10().locationCreateDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.sitemap, color: COLOR_CLICK),
|
|
||||||
trailing: FaIcon(FontAwesomeIcons.circlePlus, color: COLOR_CLICK),
|
|
||||||
onTap: () async {
|
|
||||||
_newLocation(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().stockItemCreate),
|
|
||||||
subtitle: Text(L10().stockItemCreateDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.boxesStacked, color: COLOR_CLICK),
|
|
||||||
trailing: FaIcon(FontAwesomeIcons.circlePlus, color: COLOR_CLICK),
|
|
||||||
onTap: () async {
|
|
||||||
_newStockItem(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (location != null) {
|
|
||||||
|
|
||||||
// Scan stock item into location
|
|
||||||
if (api.checkPermission("stock", "change")) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().barcodeScanItem),
|
|
||||||
subtitle: Text(L10().barcodeScanInItems),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.rightLeft, color: COLOR_CLICK),
|
|
||||||
trailing: Icon(Icons.qr_code, color: COLOR_CLICK),
|
|
||||||
onTap: () {
|
|
||||||
|
|
||||||
var _loc = location;
|
|
||||||
|
|
||||||
if (_loc != null) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) =>
|
|
||||||
InvenTreeQRView(
|
|
||||||
StockLocationScanInItemsHandler(_loc)))
|
|
||||||
).then((value) {
|
|
||||||
refresh(context);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scan this location into another one
|
|
||||||
if (api.checkPermission("stock_location", "change")) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().transferStockLocation),
|
|
||||||
subtitle: Text(L10().transferStockLocationDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.rightToBracket, color: COLOR_CLICK),
|
|
||||||
trailing: Icon(Icons.qr_code, color: COLOR_CLICK),
|
|
||||||
onTap: () {
|
|
||||||
var _loc = location;
|
|
||||||
|
|
||||||
if (_loc != null) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) =>
|
|
||||||
InvenTreeQRView(
|
|
||||||
ScanParentLocationHandler(_loc)))
|
|
||||||
).then((value) {
|
|
||||||
refresh(context);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.supportModernBarcodes) {
|
|
||||||
tiles.add(
|
|
||||||
customBarcodeActionTile(context, this, location!.customBarcode, "stocklocation", location!.pk)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tiles.length <= 1) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(
|
|
||||||
L10().actionsNone,
|
|
||||||
style: TextStyle(
|
|
||||||
fontStyle: FontStyle.italic
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ class _StockLocationListState extends RefreshableState<StockLocationList> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
@ -42,7 +42,7 @@ class _StockLocationListState extends RefreshableState<StockLocationList> {
|
|||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().stockLocations;
|
String getAppBarTitle() => L10().stockLocations;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
@ -24,10 +24,7 @@ class _NotificationState extends RefreshableState<NotificationWidget> {
|
|||||||
List<InvenTreeNotification> notifications = [];
|
List<InvenTreeNotification> notifications = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
String getAppBarTitle() => L10().notifications;
|
||||||
// No app bar for the notification widget
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> request (BuildContext context) async {
|
Future<void> request (BuildContext context) async {
|
||||||
|
@ -409,19 +409,21 @@ abstract class PaginatedSearchState<T extends PaginatedSearchWidget> extends Sta
|
|||||||
*/
|
*/
|
||||||
Widget buildSearchInput(BuildContext context) {
|
Widget buildSearchInput(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
leading: orderingOptions.isEmpty ? null : GestureDetector(
|
trailing: orderingOptions.isEmpty ? null : GestureDetector(
|
||||||
child: FaIcon(FontAwesomeIcons.sort, color: COLOR_CLICK),
|
child: FaIcon(FontAwesomeIcons.sort, color: COLOR_CLICK),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
_saveOrderingOptions(context);
|
_saveOrderingOptions(context);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
trailing: GestureDetector(
|
leading: GestureDetector(
|
||||||
child: FaIcon(
|
child: FaIcon(
|
||||||
searchController.text.isEmpty ? FontAwesomeIcons.magnifyingGlass : FontAwesomeIcons.deleteLeft,
|
searchController.text.isEmpty ? FontAwesomeIcons.magnifyingGlass : FontAwesomeIcons.deleteLeft,
|
||||||
color: searchController.text.isNotEmpty ? COLOR_DANGER : COLOR_CLICK,
|
color: searchController.text.isNotEmpty ? COLOR_DANGER : null,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
searchController.clear();
|
if (searchController.text.isEmpty) {
|
||||||
|
searchController.clear();
|
||||||
|
}
|
||||||
updateSearchTerm();
|
updateSearchTerm();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
|
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
@ -65,39 +66,62 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
int variantCount = 0;
|
int variantCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().partDetails;
|
String getAppBarTitle() => L10().partDetails;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if (api.checkPermission("part", "view")) {
|
|
||||||
actions.add(
|
|
||||||
IconButton(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.globe),
|
|
||||||
onPressed: _openInvenTreePage,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (api.checkPermission("part", "change")) {
|
if (api.checkPermission("part", "change")) {
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
icon: Icon(Icons.edit_square),
|
||||||
tooltip: L10().edit,
|
tooltip: L10().editPart,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_editPartDialog(context);
|
_editPartDialog(context);
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("part", "change")) {
|
||||||
|
if (api.supportModernBarcodes) {
|
||||||
|
actions.add(
|
||||||
|
customBarcodeAction(
|
||||||
|
context, this,
|
||||||
|
widget.part.customBarcode, "part",
|
||||||
|
widget.part.pk
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openInvenTreePage() async {
|
@override
|
||||||
part.goToInvenTreePage();
|
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("stock", "add")) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.box),
|
||||||
|
label: L10().stockItemCreate,
|
||||||
|
onTap: () {
|
||||||
|
_newStockItem(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -144,20 +168,9 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
|
|
||||||
// Request the number of parameters for this part
|
// Request the number of parameters for this part
|
||||||
if (api.supportsPartParameters) {
|
if (api.supportsPartParameters) {
|
||||||
|
|
||||||
showParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool;
|
showParameters = await InvenTreeSettingsManager().getValue(INV_PART_SHOW_PARAMETERS, true) as bool;
|
||||||
|
} else {
|
||||||
InvenTreePartParameter().count(
|
showParameters = false;
|
||||||
filters: {
|
|
||||||
"part": part.pk.toString(),
|
|
||||||
}
|
|
||||||
).then((int value) {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
parameterCount = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request the number of attachments
|
// Request the number of attachments
|
||||||
@ -394,18 +407,13 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10().availableStock),
|
title: Text(L10().availableStock),
|
||||||
subtitle: Text(L10().stockDetails),
|
subtitle: Text(L10().stockDetails),
|
||||||
leading: FaIcon(FontAwesomeIcons.boxesStacked, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.boxesStacked),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
part.stockString(),
|
part.stockString(),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
tabIndex = 1;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -436,14 +444,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
title: Text(L10().billOfMaterials),
|
title: Text(L10().billOfMaterials),
|
||||||
leading: FaIcon(FontAwesomeIcons.tableList, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.tableList, color: COLOR_CLICK),
|
||||||
trailing: Text(bomCount.toString()),
|
trailing: Text(bomCount.toString()),
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => BillOfMaterialsWidget(part)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -543,24 +543,6 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showParameters) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().parameters),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.tableList, color: COLOR_CLICK),
|
|
||||||
trailing: parameterCount > 0 ? Text(parameterCount.toString()) : null,
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => PartParameterWidget(part)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notes field
|
// Notes field
|
||||||
tiles.add(
|
tiles.add(
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -682,89 +664,49 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Widget> actionTiles(BuildContext context) {
|
|
||||||
List<Widget> tiles = [];
|
|
||||||
|
|
||||||
tiles.add(headerTile());
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().stockItemCreate),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.box),
|
|
||||||
onTap: () {
|
|
||||||
_newStockItem(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (api.supportModernBarcodes) {
|
|
||||||
tiles.add(
|
|
||||||
customBarcodeActionTile(context, this, part.customBarcode, "part", part.pk)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
int stockItemCount = 0;
|
|
||||||
|
|
||||||
Widget getSelectedWidget(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return Center(
|
|
||||||
child: ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: partTiles()
|
|
||||||
).toList()
|
|
||||||
),
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return PaginatedStockItemList(
|
|
||||||
{"part": "${part.pk}"},
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
case 2:
|
|
||||||
return Center(
|
|
||||||
child: ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: actionTiles(context)
|
|
||||||
).toList()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return Center();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
List<Widget> getTabIcons(BuildContext context) {
|
||||||
return BottomNavigationBar(
|
List<Widget> icons = [
|
||||||
currentIndex: tabIndex,
|
Tab(text: L10().details),
|
||||||
onTap: onTabSelectionChanged,
|
Tab(text: L10().stock)
|
||||||
items: <BottomNavigationBarItem> [
|
];
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
if (showBom && part.isAssembly) {
|
||||||
label: L10().details,
|
icons.add(Tab(text: L10().bom));
|
||||||
),
|
}
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.boxesStacked),
|
if (showParameters) {
|
||||||
label: L10().stock
|
icons.add(Tab(text: L10().parameters));
|
||||||
),
|
}
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
return icons;
|
||||||
label: L10().actions,
|
|
||||||
),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
List<Widget> getTabs(BuildContext context) {
|
||||||
return getSelectedWidget(tabIndex);
|
List<Widget> tabs = [
|
||||||
|
Center(
|
||||||
|
child: ListView(
|
||||||
|
children: ListTile.divideTiles(
|
||||||
|
context: context,
|
||||||
|
tiles: partTiles()
|
||||||
|
).toList()
|
||||||
|
)
|
||||||
|
),
|
||||||
|
PaginatedStockItemList({"part": part.pk.toString()}, true)
|
||||||
|
];
|
||||||
|
|
||||||
|
if (showBom && part.isAssembly) {
|
||||||
|
tabs.add(PaginatedBomList({"part": part.pk.toString()}, showSearch: true, isParentPart: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showParameters) {
|
||||||
|
tabs.add(PaginatedParameterList({"part": part.pk.toString()}, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
return tabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,10 +35,10 @@ class _PartImageState extends RefreshableState<PartImageWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => part.fullname;
|
String getAppBarTitle() => part.fullname;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
|
@ -36,10 +36,10 @@ class _PartListState extends RefreshableState<PartList> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => title.isNotEmpty ? title : L10().parts;
|
String getAppBarTitle() => title.isNotEmpty ? title : L10().parts;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -30,10 +30,10 @@ class _PartNotesState extends RefreshableState<PartNotesWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().partNotes;
|
String getAppBarTitle() => L10().partNotes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ class _ParameterWidgetState extends RefreshableState<PartParameterWidget> {
|
|||||||
_ParameterWidgetState();
|
_ParameterWidgetState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) {
|
String getAppBarTitle() {
|
||||||
return L10().parameters;
|
return L10().parameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,10 +38,10 @@ class _PartSupplierState extends RefreshableState<PartSupplierWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().partSuppliers;
|
String getAppBarTitle() => L10().partSuppliers;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext contexts) {
|
List<Widget> appBarActions(BuildContext contexts) {
|
||||||
// TODO
|
// TODO
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
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";
|
||||||
import "package:one_context/one_context.dart";
|
import "package:one_context/one_context.dart";
|
||||||
|
|
||||||
@ -7,12 +6,13 @@ import "package:inventree/api.dart";
|
|||||||
import "package:inventree/api_form.dart";
|
import "package:inventree/api_form.dart";
|
||||||
import "package:inventree/app_colors.dart";
|
import "package:inventree/app_colors.dart";
|
||||||
import "package:inventree/helpers.dart";
|
import "package:inventree/helpers.dart";
|
||||||
|
import "package:inventree/l10.dart";
|
||||||
|
|
||||||
import "package:inventree/inventree/company.dart";
|
import "package:inventree/inventree/company.dart";
|
||||||
import "package:inventree/inventree/purchase_order.dart";
|
import "package:inventree/inventree/purchase_order.dart";
|
||||||
import "package:inventree/widget/attachment_widget.dart";
|
import "package:inventree/widget/attachment_widget.dart";
|
||||||
import "package:inventree/widget/company_detail.dart";
|
import "package:inventree/widget/company_detail.dart";
|
||||||
import "package:inventree/widget/refreshable_state.dart";
|
import "package:inventree/widget/refreshable_state.dart";
|
||||||
import "package:inventree/l10.dart";
|
|
||||||
import "package:inventree/widget/snacks.dart";
|
import "package:inventree/widget/snacks.dart";
|
||||||
import "package:inventree/widget/stock_list.dart";
|
import "package:inventree/widget/stock_list.dart";
|
||||||
|
|
||||||
@ -41,17 +41,17 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
int attachmentCount = 0;
|
int attachmentCount = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().purchaseOrder;
|
String getAppBarTitle() => L10().purchaseOrder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if (InvenTreeAPI().checkPermission("purchase_order", "change")) {
|
if (InvenTreeAPI().checkPermission("purchase_order", "change")) {
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
icon: Icon(Icons.edit_square),
|
||||||
tooltip: L10().edit,
|
tooltip: L10().purchaseOrderEdit,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
editOrder(context);
|
editOrder(context);
|
||||||
}
|
}
|
||||||
@ -145,12 +145,6 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
title: Text(L10().lineItems),
|
title: Text(L10().lineItems),
|
||||||
leading: FaIcon(FontAwesomeIcons.clipboardList, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.clipboardList, color: COLOR_CLICK),
|
||||||
trailing: Text("${order.lineItemCount}"),
|
trailing: Text("${order.lineItemCount}"),
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
// Switch to the "line items" tab
|
|
||||||
tabIndex = 1;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
|
|
||||||
tiles.add(ListTile(
|
tiles.add(ListTile(
|
||||||
@ -165,12 +159,6 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
title: Text(L10().received),
|
title: Text(L10().received),
|
||||||
leading: FaIcon(FontAwesomeIcons.clipboardCheck, color: COLOR_CLICK),
|
leading: FaIcon(FontAwesomeIcons.clipboardCheck, color: COLOR_CLICK),
|
||||||
trailing: Text("${completedLines}"),
|
trailing: Text("${completedLines}"),
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
// Switch to the "received items" tab
|
|
||||||
tabIndex = 2;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
));
|
));
|
||||||
|
|
||||||
if (order.issueDate.isNotEmpty) {
|
if (order.issueDate.isNotEmpty) {
|
||||||
@ -373,56 +361,21 @@ class _PurchaseOrderDetailState extends RefreshableState<PurchaseOrderDetailWidg
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
List<Widget> getTabIcons(BuildContext context) {
|
||||||
|
return [
|
||||||
return Center(
|
Tab(text: L10().details),
|
||||||
child: getSelectedWidget(context, tabIndex),
|
Tab(text: L10().lineItems),
|
||||||
);
|
Tab(text: L10().received)
|
||||||
}
|
];
|
||||||
|
|
||||||
Widget getSelectedWidget(BuildContext context, int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return ListView(
|
|
||||||
children: orderTiles(context)
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return ListView(
|
|
||||||
children: lineTiles(context)
|
|
||||||
);
|
|
||||||
case 2:
|
|
||||||
// Stock items received against this order
|
|
||||||
Map<String, String> filters = {
|
|
||||||
"purchase_order": "${order.pk}"
|
|
||||||
};
|
|
||||||
|
|
||||||
return PaginatedStockItemList(filters, true);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return ListView();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
List<Widget> getTabs(BuildContext context) {
|
||||||
return BottomNavigationBar(
|
return [
|
||||||
currentIndex: tabIndex,
|
ListView(children: orderTiles(context)),
|
||||||
onTap: onTabSelectionChanged,
|
ListView(children: lineTiles(context)),
|
||||||
items: [
|
PaginatedStockItemList({"purchase_order": order.pk.toString()}, true),
|
||||||
BottomNavigationBarItem(
|
];
|
||||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
|
||||||
label: L10().details
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.tableList),
|
|
||||||
label: L10().lineItems,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.boxesStacked),
|
|
||||||
label: L10().stockItems
|
|
||||||
)
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -33,10 +33,10 @@ class _PurchaseOrderListWidgetState extends RefreshableState<PurchaseOrderListWi
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().purchaseOrders;
|
String getAppBarTitle() => L10().purchaseOrders;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
|
import "package:inventree/barcode.dart";
|
||||||
|
|
||||||
import "package:inventree/widget/back.dart";
|
import "package:inventree/widget/back.dart";
|
||||||
import "package:inventree/widget/drawer.dart";
|
import "package:inventree/widget/drawer.dart";
|
||||||
|
import "package:inventree/widget/search.dart";
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -11,17 +14,22 @@ import "package:inventree/widget/drawer.dart";
|
|||||||
*/
|
*/
|
||||||
mixin BaseWidgetProperties {
|
mixin BaseWidgetProperties {
|
||||||
|
|
||||||
// Return a list of appBar actions (default = None)
|
/*
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [];
|
* Return a list of appBar actions
|
||||||
|
* By default, no appBar actions are available
|
||||||
|
*/
|
||||||
|
List<Widget> appBarActions(BuildContext context) => [];
|
||||||
|
|
||||||
// Return a title for the appBar
|
// Return a title for the appBar
|
||||||
String getAppBarTitle(BuildContext context) { return "--- app bar ---"; }
|
String getAppBarTitle() { return "--- app bar ---"; }
|
||||||
|
|
||||||
// Function to construct a drawer (override if needed)
|
// Function to construct a drawer (override if needed)
|
||||||
Widget getDrawer(BuildContext context) {
|
Widget getDrawer(BuildContext context) {
|
||||||
return InvenTreeDrawer(context);
|
return InvenTreeDrawer(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Widget> getTabs(BuildContext context) => [];
|
||||||
|
|
||||||
// Function to construct a body (MUST BE PROVIDED)
|
// Function to construct a body (MUST BE PROVIDED)
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
|
|
||||||
@ -29,18 +37,145 @@ mixin BaseWidgetProperties {
|
|||||||
return ListView();
|
return ListView();
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget? getBottomNavBar(BuildContext context) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct the top AppBar for this view
|
||||||
|
*/
|
||||||
AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
||||||
|
|
||||||
|
List<Widget> tabs = getTabIcons(context);
|
||||||
|
|
||||||
return AppBar(
|
return AppBar(
|
||||||
title: Text(getAppBarTitle(context)),
|
centerTitle: false,
|
||||||
actions: getAppBarActions(context),
|
bottom: tabs.isEmpty ? null : TabBar(tabs: tabs),
|
||||||
|
title: Text(getAppBarTitle()),
|
||||||
|
actions: appBarActions(context),
|
||||||
leading: backButton(context, key),
|
leading: backButton(context, key),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct a global navigation bar at the bottom of the screen
|
||||||
|
* - Button to access navigation menu
|
||||||
|
* - Button to access global search
|
||||||
|
* - Button to access barcode scan
|
||||||
|
*/
|
||||||
|
BottomAppBar? buildBottomAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
||||||
|
|
||||||
|
const double iconSize = 32;
|
||||||
|
const Color iconColor = Colors.blueGrey;
|
||||||
|
|
||||||
|
List<Widget> icons = [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.menu, color: iconColor),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
if (key.currentState != null) {
|
||||||
|
key.currentState!.openDrawer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.search, color: iconColor),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
if (InvenTreeAPI().checkConnection()) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => SearchWidget(true)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.qr_code_scanner, color: iconColor),
|
||||||
|
iconSize: iconSize,
|
||||||
|
onPressed: () {
|
||||||
|
if (InvenTreeAPI().checkConnection()) {
|
||||||
|
scanQrCode(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
];
|
||||||
|
|
||||||
|
return BottomAppBar(
|
||||||
|
shape: CircularNotchedRectangle(),
|
||||||
|
notchMargin: 20,
|
||||||
|
child: IconTheme(
|
||||||
|
data: IconThemeData(color: Theme.of(context).colorScheme.onPrimary),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: icons,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build out a set of SpeedDialChild widgets, to serve as "actions" for this view
|
||||||
|
* Should be re-implemented by particular view with the required actions
|
||||||
|
* By default, returns an empty list, and thus nothing will be rendered
|
||||||
|
*/
|
||||||
|
List<SpeedDialChild> actionButtons(BuildContext context) => [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build out a set of barcode actions available for this view
|
||||||
|
*/
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) => [];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Build out action buttons for a given widget
|
||||||
|
*/
|
||||||
|
Widget? buildSpeedDial(BuildContext context) {
|
||||||
|
|
||||||
|
final actions = actionButtons(context);
|
||||||
|
final barcodeActions = barcodeButtons(context);
|
||||||
|
|
||||||
|
if (actions.isEmpty && barcodeActions.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> children = [];
|
||||||
|
|
||||||
|
if (barcodeActions.isNotEmpty) {
|
||||||
|
children.add(
|
||||||
|
SpeedDial(
|
||||||
|
icon: Icons.qr_code_scanner,
|
||||||
|
activeIcon: Icons.close,
|
||||||
|
children: barcodeActions,
|
||||||
|
spacing: 14,
|
||||||
|
childPadding: const EdgeInsets.all(5),
|
||||||
|
spaceBetweenChildren: 15,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actions.isNotEmpty) {
|
||||||
|
children.add(
|
||||||
|
SpeedDial(
|
||||||
|
icon: Icons.more_horiz,
|
||||||
|
activeIcon: Icons.close,
|
||||||
|
children: actions,
|
||||||
|
spacing: 14,
|
||||||
|
childPadding: const EdgeInsets.all(5),
|
||||||
|
spaceBetweenChildren: 15,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Wrap(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
children: children,
|
||||||
|
spacing: 15,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return list of "tabs" for this widget
|
||||||
|
List<Widget> getTabIcons(BuildContext context) => [];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -57,9 +192,6 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
|
|||||||
// Storage for context once "Build" is called
|
// Storage for context once "Build" is called
|
||||||
late BuildContext? _context;
|
late BuildContext? _context;
|
||||||
|
|
||||||
// Current tab index (used for widgets which display bottom tabs)
|
|
||||||
int tabIndex = 0;
|
|
||||||
|
|
||||||
// Bool indicator
|
// Bool indicator
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
|
||||||
@ -68,16 +200,6 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
|
|||||||
// Helper function to return API instance
|
// Helper function to return API instance
|
||||||
InvenTreeAPI get api => InvenTreeAPI();
|
InvenTreeAPI get api => InvenTreeAPI();
|
||||||
|
|
||||||
// Update current tab selection
|
|
||||||
void onTabSelectionChanged(int index) {
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
tabIndex = index;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -124,21 +246,38 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> with
|
|||||||
// Save the context for future use
|
// Save the context for future use
|
||||||
_context = context;
|
_context = context;
|
||||||
|
|
||||||
return Scaffold(
|
List<Widget> tabs = getTabIcons(context);
|
||||||
|
|
||||||
|
Widget body = tabs.isEmpty ? getBody(context) : TabBarView(children: getTabs(context));
|
||||||
|
|
||||||
|
Scaffold view = Scaffold(
|
||||||
key: refreshableKey,
|
key: refreshableKey,
|
||||||
appBar: buildAppBar(context, refreshableKey),
|
appBar: buildAppBar(context, refreshableKey),
|
||||||
drawer: getDrawer(context),
|
drawer: getDrawer(context),
|
||||||
|
floatingActionButton: buildSpeedDial(context),
|
||||||
|
floatingActionButtonLocation: FloatingActionButtonLocation
|
||||||
|
.miniEndDocked,
|
||||||
body: Builder(
|
body: Builder(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
refresh(context);
|
refresh(context);
|
||||||
},
|
},
|
||||||
child: getBody(context)
|
child: body
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
bottomNavigationBar: getBottomNavBar(context),
|
bottomNavigationBar: buildBottomAppBar(context, refreshableKey),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Default implementation is *not* tabbed
|
||||||
|
if (tabs.isNotEmpty) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: tabs.length,
|
||||||
|
child: view,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -54,7 +54,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().search;
|
String getAppBarTitle() => L10().search;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
|
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
@ -43,42 +44,34 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
_StockItemDisplayState();
|
_StockItemDisplayState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().stockItem;
|
String getAppBarTitle() => L10().stockItem;
|
||||||
|
|
||||||
bool stockShowHistory = false;
|
bool stockShowHistory = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if (InvenTreeAPI().checkPermission("stock", "view")) {
|
if (api.supportsMixin("locate")) {
|
||||||
actions.add(
|
|
||||||
IconButton(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.globe),
|
|
||||||
onPressed: _openInvenTreePage,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InvenTreeAPI().supportsMixin("locate")) {
|
|
||||||
actions.add(
|
|
||||||
IconButton(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.magnifyingGlassLocation),
|
|
||||||
tooltip: L10().locateItem,
|
|
||||||
onPressed: () async {
|
|
||||||
InvenTreeAPI().locateItemOrLocation(context, item: widget.item.pk);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (InvenTreeAPI().checkPermission("stock", "change")) {
|
|
||||||
actions.add(
|
actions.add(
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
icon: Icon(Icons.travel_explore),
|
||||||
tooltip: L10().edit,
|
tooltip: L10().locateItem,
|
||||||
onPressed: () { _editStockItem(context); },
|
onPressed: () async {
|
||||||
|
api.locateItemOrLocation(context, item: widget.item.pk);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.checkPermission("stock", "change")) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit_square),
|
||||||
|
tooltip: L10().editItem,
|
||||||
|
onPressed: () {
|
||||||
|
_editStockItem(context);
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -86,8 +79,115 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
return actions;
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _openInvenTreePage() async {
|
@override
|
||||||
widget.item.goToInvenTreePage();
|
List<SpeedDialChild> actionButtons(BuildContext context) {
|
||||||
|
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("stock", "change")) {
|
||||||
|
|
||||||
|
// Stock adjustment actions available if item is *not* serialized
|
||||||
|
if (!widget.item.isSerialized()) {
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.circleCheck, color: Colors.blue),
|
||||||
|
label: L10().countStock,
|
||||||
|
onTap: _countStockDialog,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.circleMinus, color: Colors.red),
|
||||||
|
label: L10().removeStock,
|
||||||
|
onTap: _removeStockDialog,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.circlePlus, color: Colors.green),
|
||||||
|
label: L10().addStock,
|
||||||
|
onTap: _addStockDialog,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transfer item
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.trolley),
|
||||||
|
label: L10().transferStock,
|
||||||
|
onTap: () {
|
||||||
|
_transferStockDialog(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (labels.isNotEmpty) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.print),
|
||||||
|
label: L10().printLabel,
|
||||||
|
onTap: () {
|
||||||
|
_printLabel(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (api.checkPermission("stock", "delete")) {
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: FaIcon(FontAwesomeIcons.trashCan, color: Colors.red),
|
||||||
|
label: L10().stockItemDelete,
|
||||||
|
onTap: () {
|
||||||
|
_deleteItem(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("stock", "change")) {
|
||||||
|
// Scan item into location
|
||||||
|
actions.add(
|
||||||
|
SpeedDialChild(
|
||||||
|
child: Icon(Icons.qr_code_scanner),
|
||||||
|
label: L10().scanIntoLocation,
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(builder: (context) =>
|
||||||
|
InvenTreeQRView(
|
||||||
|
StockItemScanIntoLocationHandler(widget.item)))
|
||||||
|
).then((ctx) {
|
||||||
|
refresh(context);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (api.supportModernBarcodes) {
|
||||||
|
actions.add(
|
||||||
|
customBarcodeAction(
|
||||||
|
context, this,
|
||||||
|
widget.item.customBarcode,
|
||||||
|
"stockitem", widget.item.pk
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is label printing enabled for this StockItem?
|
// Is label printing enabled for this StockItem?
|
||||||
@ -740,170 +840,13 @@ class _StockItemDisplayState extends RefreshableState<StockDetailWidget> {
|
|||||||
return tiles;
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> actionTiles(BuildContext context) {
|
|
||||||
List<Widget> tiles = [];
|
|
||||||
|
|
||||||
tiles.add(headerTile());
|
|
||||||
|
|
||||||
// First check that the user has the required permissions to adjust stock
|
|
||||||
if (!InvenTreeAPI().checkPermission("stock", "change")) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().permissionRequired),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.userXmark)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
subtitle: Text(L10().permissionAccountDenied),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// "Count" is not available for serialized stock
|
|
||||||
if (!widget.item.isSerialized()) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().countStock),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.circleCheck, color: COLOR_CLICK),
|
|
||||||
onTap: _countStockDialog,
|
|
||||||
trailing: Text(widget.item.quantityString(includeUnits: true)),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().removeStock),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.circleMinus, color: COLOR_CLICK),
|
|
||||||
onTap: _removeStockDialog,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().addStock),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.circlePlus, color: COLOR_CLICK),
|
|
||||||
onTap: _addStockDialog,
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().transferStock),
|
|
||||||
subtitle: Text(L10().transferStockDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.rightLeft, color: COLOR_CLICK),
|
|
||||||
onTap: () { _transferStockDialog(context); },
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Scan item into a location
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().scanIntoLocation),
|
|
||||||
subtitle: Text(L10().scanIntoLocationDetail),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.rightLeft, color: COLOR_CLICK),
|
|
||||||
trailing: Icon(Icons.qr_code_scanner),
|
|
||||||
onTap: () {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(builder: (context) => InvenTreeQRView(StockItemScanIntoLocationHandler(widget.item)))
|
|
||||||
).then((ctx) {
|
|
||||||
refresh(context);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (InvenTreeAPI().supportModernBarcodes || widget.item.customBarcode.isEmpty) {
|
|
||||||
tiles.add(customBarcodeActionTile(context, this, widget.item.customBarcode, "stockitem", widget.item.pk));
|
|
||||||
} else {
|
|
||||||
// Note: Custom legacy barcodes (only for StockItem model) are handled differently
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().barcodeUnassign),
|
|
||||||
leading: Icon(Icons.qr_code, color: COLOR_CLICK),
|
|
||||||
onTap: () async {
|
|
||||||
await widget.item.update(values: {"uid": ""});
|
|
||||||
refresh(context);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Print label (if label printing plugins exist)
|
|
||||||
if (labels.isNotEmpty) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10().printLabel),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.print, color: COLOR_CLICK),
|
|
||||||
onTap: () {
|
|
||||||
_printLabel(context);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the user has permission to delete this stock item
|
|
||||||
if (InvenTreeAPI().checkPermission("stock", "delete")) {
|
|
||||||
tiles.add(
|
|
||||||
ListTile(
|
|
||||||
title: Text("Delete Stock Item"),
|
|
||||||
leading: FaIcon(FontAwesomeIcons.trashCan, color: COLOR_DANGER),
|
|
||||||
onTap: () {
|
|
||||||
_deleteItem(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
|
||||||
return BottomNavigationBar(
|
|
||||||
currentIndex: tabIndex,
|
|
||||||
onTap: onTabSelectionChanged,
|
|
||||||
items: <BottomNavigationBarItem> [
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
|
||||||
label: L10().details,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
|
||||||
label: L10().actions, ),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getSelectedWidget(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: detailTiles()
|
|
||||||
).toList(),
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: actionTiles(context)
|
|
||||||
).toList()
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return ListView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return getSelectedWidget(tabIndex);
|
return ListView(
|
||||||
|
children: ListTile.divideTiles(
|
||||||
|
context: context,
|
||||||
|
tiles: detailTiles()
|
||||||
|
).toList()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -24,7 +24,7 @@ class _StockItemHistoryDisplayState extends RefreshableState<StockItemHistoryWid
|
|||||||
final InvenTreeStockItem item;
|
final InvenTreeStockItem item;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().stockItemHistory;
|
String getAppBarTitle() => L10().stockItemHistory;
|
||||||
|
|
||||||
List<InvenTreeStockItemHistory> history = [];
|
List<InvenTreeStockItemHistory> history = [];
|
||||||
|
|
||||||
|
@ -28,10 +28,10 @@ class _StockItemTestResultDisplayState extends RefreshableState<StockItemTestRes
|
|||||||
_StockItemTestResultDisplayState(this.item);
|
_StockItemTestResultDisplayState(this.item);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().testResults;
|
String getAppBarTitle() => L10().testResults;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
return [
|
return [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.circlePlus),
|
icon: FaIcon(FontAwesomeIcons.circlePlus),
|
||||||
|
@ -30,10 +30,10 @@ class _StockListState extends RefreshableState<StockItemList> {
|
|||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().stockItems;
|
String getAppBarTitle() => L10().stockItems;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -27,7 +27,7 @@ class _StockNotesState extends RefreshableState<StockNotesWidget> {
|
|||||||
final InvenTreeStockItem item;
|
final InvenTreeStockItem item;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().stockItemNotes;
|
String getAppBarTitle() => L10().stockItemNotes;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> request(BuildContext context) async {
|
Future<void> request(BuildContext context) async {
|
||||||
@ -37,7 +37,7 @@ class _StockNotesState extends RefreshableState<StockNotesWidget> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
List<Widget> actions = [];
|
List<Widget> actions = [];
|
||||||
|
|
||||||
if (InvenTreeAPI().checkPermission("stock", "change")) {
|
if (InvenTreeAPI().checkPermission("stock", "change")) {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_speed_dial/flutter_speed_dial.dart";
|
||||||
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
import "package:font_awesome_flutter/font_awesome_flutter.dart";
|
||||||
|
|
||||||
import "package:inventree/api.dart";
|
import "package:inventree/api.dart";
|
||||||
@ -35,39 +36,64 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
|
|||||||
_SupplierPartDisplayState();
|
_SupplierPartDisplayState();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().supplierPart;
|
String getAppBarTitle() => L10().supplierPart;
|
||||||
|
|
||||||
@override
|
|
||||||
List<Widget> getAppBarActions(BuildContext context) {
|
|
||||||
List<Widget> actions = [];
|
|
||||||
|
|
||||||
actions.add(
|
|
||||||
IconButton(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.penToSquare),
|
|
||||||
tooltip: L10().edit,
|
|
||||||
onPressed: () {
|
|
||||||
editSupplierPart(context);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
return actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Launch a form to edit the current SupplierPart instance
|
* Launch a form to edit the current SupplierPart instance
|
||||||
*/
|
*/
|
||||||
Future<void> editSupplierPart(BuildContext context) async {
|
Future<void> editSupplierPart(BuildContext context) async {
|
||||||
widget.supplierPart.editForm(
|
widget.supplierPart.editForm(
|
||||||
context,
|
context,
|
||||||
L10().supplierPartEdit,
|
L10().supplierPartEdit,
|
||||||
onSuccess: (data) async {
|
onSuccess: (data) async {
|
||||||
refresh(context);
|
refresh(context);
|
||||||
showSnackIcon(L10().supplierPartUpdated, success: true);
|
showSnackIcon(L10().supplierPartUpdated, success: true);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<SpeedDialChild> barcodeButtons(BuildContext context) {
|
||||||
|
List<SpeedDialChild> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("purchase_order", "change") ||
|
||||||
|
api.checkPermission("sales_order", "change") ||
|
||||||
|
api.checkPermission("return_order", "change")) {
|
||||||
|
|
||||||
|
actions.add(
|
||||||
|
customBarcodeAction(
|
||||||
|
context, this,
|
||||||
|
widget.supplierPart.customBarcode,
|
||||||
|
"supplierpart",
|
||||||
|
widget.supplierPart.pk
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Widget> appBarActions(BuildContext context) {
|
||||||
|
List<Widget> actions = [];
|
||||||
|
|
||||||
|
if (api.checkPermission("purchase_order", "change") ||
|
||||||
|
api.checkPermission("sales_order", "change") ||
|
||||||
|
api.checkPermission("return_order", "change")) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.edit_square),
|
||||||
|
tooltip: L10().edit,
|
||||||
|
onPressed: () {
|
||||||
|
editSupplierPart(context);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> request(BuildContext context) async {
|
Future<void> request(BuildContext context) async {
|
||||||
final bool result = widget.supplierPart.pk > 0 && await widget.supplierPart.reload();
|
final bool result = widget.supplierPart.pk > 0 && await widget.supplierPart.reload();
|
||||||
@ -179,60 +205,14 @@ class _SupplierPartDisplayState extends RefreshableState<SupplierPartDetailWidge
|
|||||||
return tiles;
|
return tiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Return a list of actions which can be performed for this SupplierPart
|
|
||||||
*/
|
|
||||||
List<Widget> actionTiles(BuildContext context) {
|
|
||||||
List<Widget> tiles = [];
|
|
||||||
|
|
||||||
tiles.add(
|
|
||||||
customBarcodeActionTile(context, this, widget.supplierPart.customBarcode, "supplierpart", widget.supplierPart.pk)
|
|
||||||
);
|
|
||||||
|
|
||||||
return tiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget getSelectedWidget(int index) {
|
|
||||||
switch (index) {
|
|
||||||
case 0:
|
|
||||||
return ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: detailTiles(context),
|
|
||||||
).toList()
|
|
||||||
);
|
|
||||||
case 1:
|
|
||||||
return ListView(
|
|
||||||
children: ListTile.divideTiles(
|
|
||||||
context: context,
|
|
||||||
tiles: actionTiles(context)
|
|
||||||
).toList()
|
|
||||||
);
|
|
||||||
default:
|
|
||||||
return ListView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget getBody(BuildContext context) {
|
Widget getBody(BuildContext context) {
|
||||||
return getSelectedWidget(tabIndex);
|
return ListView(
|
||||||
}
|
children: ListTile.divideTiles(
|
||||||
|
context: context,
|
||||||
@override
|
tiles: detailTiles(context),
|
||||||
Widget getBottomNavBar(BuildContext context) {
|
).toList()
|
||||||
return BottomNavigationBar(
|
|
||||||
currentIndex: tabIndex,
|
|
||||||
onTap: onTabSelectionChanged,
|
|
||||||
items: [
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.circleInfo),
|
|
||||||
label: L10().details,
|
|
||||||
),
|
|
||||||
BottomNavigationBarItem(
|
|
||||||
icon: FaIcon(FontAwesomeIcons.wrench),
|
|
||||||
label: L10().actions
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -29,12 +29,12 @@ class SupplierPartList extends StatefulWidget {
|
|||||||
class _SupplierPartListState extends RefreshableState<SupplierPartList> {
|
class _SupplierPartListState extends RefreshableState<SupplierPartList> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String getAppBarTitle(BuildContext context) => L10().supplierParts;
|
String getAppBarTitle() => L10().supplierParts;
|
||||||
|
|
||||||
bool showFilterOptions = false;
|
bool showFilterOptions = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Widget> getAppBarActions(BuildContext context) => [
|
List<Widget> appBarActions(BuildContext context) => [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: FaIcon(FontAwesomeIcons.filter),
|
icon: FaIcon(FontAwesomeIcons.filter),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
|
@ -395,6 +395,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.7"
|
version: "2.0.7"
|
||||||
|
flutter_speed_dial:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_speed_dial
|
||||||
|
sha256: "41d7ad0bc224248637b3a5e0b9083e912a75445bdb450cf82b8ed06d7af7c61d"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "6.2.0"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -24,6 +24,7 @@ dependencies:
|
|||||||
flutter_localized_locales: ^2.0.4
|
flutter_localized_locales: ^2.0.4
|
||||||
flutter_markdown: ^0.6.13+1 # Rendering markdown
|
flutter_markdown: ^0.6.13+1 # Rendering markdown
|
||||||
flutter_overlay_loader: ^2.0.0 # Overlay screen support
|
flutter_overlay_loader: ^2.0.0 # Overlay screen support
|
||||||
|
flutter_speed_dial: ^6.2.0 # Speed dial / FAB implementation
|
||||||
font_awesome_flutter: ^10.3.0 # FontAwesome icon set
|
font_awesome_flutter: ^10.3.0 # FontAwesome icon set
|
||||||
http: ^0.13.4
|
http: ^0.13.4
|
||||||
image_picker: ^0.8.6+1 # Select or take photos
|
image_picker: ^0.8.6+1 # Select or take photos
|
||||||
|
Loading…
x
Reference in New Issue
Block a user