mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 13:25:40 +00:00 
			
		
		
		
	| @@ -230,6 +230,9 @@ class InvenTreeAPI { | |||||||
|  |  | ||||||
|   int get apiVersion => _apiVersion; |   int get apiVersion => _apiVersion; | ||||||
|  |  | ||||||
|  |   // Notification support requires API v25 or newer | ||||||
|  |   bool get supportsNotifications => isConnected() && apiVersion >= 25; | ||||||
|  |  | ||||||
|   // Are plugins enabled on the server? |   // Are plugins enabled on the server? | ||||||
|   bool _pluginsEnabled = false; |   bool _pluginsEnabled = false; | ||||||
|  |  | ||||||
| @@ -428,6 +431,7 @@ class InvenTreeAPI { | |||||||
|  |  | ||||||
|     // Return the received token |     // Return the received token | ||||||
|     _token = (data["token"] ?? "") as String; |     _token = (data["token"] ?? "") as String; | ||||||
|  |  | ||||||
|     print("Received token - $_token"); |     print("Received token - $_token"); | ||||||
|  |  | ||||||
|     // Request user role information (async) |     // Request user role information (async) | ||||||
|   | |||||||
							
								
								
									
										51
									
								
								lib/inventree/notification.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/inventree/notification.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | |||||||
|  | import "package:inventree/inventree/model.dart"; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Class representing a "notification" | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | class InvenTreeNotification extends InvenTreeModel { | ||||||
|  |  | ||||||
|  |   InvenTreeNotification() : super(); | ||||||
|  |  | ||||||
|  |   InvenTreeNotification.fromJson(Map<String, dynamic> json) : super.fromJson(json); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   InvenTreeNotification createFromJson(Map<String, dynamic> json) { | ||||||
|  |     return InvenTreeNotification.fromJson(json); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String get URL => "notifications/"; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Map<String, String> defaultListFilters() { | ||||||
|  |  | ||||||
|  |     // By default, only return 'unread' notifications | ||||||
|  |     return { | ||||||
|  |       "read": "false", | ||||||
|  |     }; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   String get message => (jsondata["message"] ?? "") as String; | ||||||
|  |  | ||||||
|  |   DateTime? get creationDate { | ||||||
|  |     if (jsondata.containsKey("creation")) { | ||||||
|  |       return DateTime.tryParse((jsondata["creation"] ?? "") as String); | ||||||
|  |     } else { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /* | ||||||
|  |    * Dismiss this notification (mark as read) | ||||||
|  |    */ | ||||||
|  |   Future<void> dismiss() async { | ||||||
|  |  | ||||||
|  |     await api.post( | ||||||
|  |       "${url}read/", | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -533,7 +533,7 @@ class InvenTreeStockItem extends InvenTreeModel { | |||||||
|     Map<String, dynamic> data = {}; |     Map<String, dynamic> data = {}; | ||||||
|  |  | ||||||
|     // Note: Format of adjustment API was updated in API v14 |     // Note: Format of adjustment API was updated in API v14 | ||||||
|     if (InvenTreeAPI().supportModernStockTransactions()) { |     if (api.supportModernStockTransactions()) { | ||||||
|       // Modern (> 14) API |       // Modern (> 14) API | ||||||
|       data = { |       data = { | ||||||
|         "items": [ |         "items": [ | ||||||
| @@ -560,7 +560,7 @@ class InvenTreeStockItem extends InvenTreeModel { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Expected API return code depends on server API version |     // Expected API return code depends on server API version | ||||||
|     final int expected_response = InvenTreeAPI().supportModernStockTransactions() ? 201 : 200; |     final int expected_response = api.supportModernStockTransactions() ? 201 : 200; | ||||||
|  |  | ||||||
|     var response = await api.post( |     var response = await api.post( | ||||||
|       endpoint, |       endpoint, | ||||||
|   | |||||||
| @@ -308,6 +308,9 @@ | |||||||
|     "description": "history" |     "description": "history" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   "home": "Home", | ||||||
|  |   "@homeScreen": {}, | ||||||
|  |  | ||||||
|   "homeScreen": "Home Screen", |   "homeScreen": "Home Screen", | ||||||
|   "@homeScreen": {}, |   "@homeScreen": {}, | ||||||
|  |  | ||||||
| @@ -461,6 +464,12 @@ | |||||||
|     "description": "Notes" |     "description": "Notes" | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|  |   "notifications": "Notifications", | ||||||
|  |   "@notifications": {}, | ||||||
|  |  | ||||||
|  |    "notificationsEmpty": "No unread notifications", | ||||||
|  |    "@notificationsEmpty": {}, | ||||||
|  |  | ||||||
|   "noResponse": "No Response from Server", |   "noResponse": "No Response from Server", | ||||||
|   "@noResponse": {}, |   "@noResponse": {}, | ||||||
|  |  | ||||||
| @@ -631,6 +640,9 @@ | |||||||
|   "quantityPositive": "Quantity must be positive", |   "quantityPositive": "Quantity must be positive", | ||||||
|   "@quantityPositive": {}, |   "@quantityPositive": {}, | ||||||
|  |  | ||||||
|  |   "queryEmpty": "Enter search query", | ||||||
|  |   "@queryEmpty": {}, | ||||||
|  |  | ||||||
|   "queryNoResults": "No results for query", |   "queryNoResults": "No results for query", | ||||||
|   "@queryNoResults": {}, |   "@queryNoResults": {}, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,6 @@ Widget backButton(BuildContext context, GlobalKey<ScaffoldState> key) { | |||||||
|     onLongPress: () { |     onLongPress: () { | ||||||
|       // Display the menu |       // Display the menu | ||||||
|       key.currentState!.openDrawer(); |       key.currentState!.openDrawer(); | ||||||
|       print("hello?"); |  | ||||||
|     }, |     }, | ||||||
|     child: IconButton( |     child: IconButton( | ||||||
|       icon: BackButtonIcon(), |       icon: BackButtonIcon(), | ||||||
|   | |||||||
| @@ -174,7 +174,6 @@ class _CategoryDisplayState extends RefreshableState<CategoryDisplayWidget> { | |||||||
|           icon: FaIcon(FontAwesomeIcons.shapes), |           icon: FaIcon(FontAwesomeIcons.shapes), | ||||||
|           label: L10().parts, |           label: L10().parts, | ||||||
|         ), |         ), | ||||||
|         // TODO - Add the "actions" item back in |  | ||||||
|         BottomNavigationBarItem( |         BottomNavigationBarItem( | ||||||
|           icon: FaIcon(FontAwesomeIcons.wrench), |           icon: FaIcon(FontAwesomeIcons.wrench), | ||||||
|           label: L10().actions |           label: L10().actions | ||||||
|   | |||||||
| @@ -41,7 +41,7 @@ class InvenTreeDrawer extends StatelessWidget { | |||||||
|     Navigator.push( |     Navigator.push( | ||||||
|         context, |         context, | ||||||
|         MaterialPageRoute( |         MaterialPageRoute( | ||||||
|             builder: (context) => SearchWidget() |             builder: (context) => SearchWidget(true) | ||||||
|         ) |         ) | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -1,24 +1,29 @@ | |||||||
|  | import "dart:async"; | ||||||
|  |  | ||||||
| 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:inventree/api.dart"; | ||||||
| import "package:inventree/app_colors.dart"; | import "package:inventree/app_colors.dart"; | ||||||
|  | import "package:inventree/app_settings.dart"; | ||||||
|  | import "package:inventree/barcode.dart"; | ||||||
|  | import "package:inventree/l10.dart"; | ||||||
|  | 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/l10.dart"; |  | ||||||
| import "package:inventree/barcode.dart"; | import "package:inventree/inventree/notification.dart"; | ||||||
| import "package:inventree/api.dart"; |  | ||||||
| import "package:inventree/settings/login.dart"; |  | ||||||
| import "package:inventree/widget/category_display.dart"; | import "package:inventree/widget/category_display.dart"; | ||||||
| import "package:inventree/widget/company_list.dart"; | import "package:inventree/widget/company_list.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/search.dart"; | ||||||
| import "package:inventree/widget/snacks.dart"; | import "package:inventree/widget/snacks.dart"; | ||||||
| import "package:inventree/widget/drawer.dart"; |  | ||||||
|  |  | ||||||
| import "package:inventree/app_settings.dart"; |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class InvenTreeHomePage extends StatefulWidget { | class InvenTreeHomePage extends StatefulWidget { | ||||||
| @@ -32,15 +37,29 @@ class InvenTreeHomePage extends StatefulWidget { | |||||||
| class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | ||||||
|  |  | ||||||
|   _InvenTreeHomePageState() : super() { |   _InvenTreeHomePageState() : super() { | ||||||
|  |  | ||||||
|     // Load display settings |     // Load display settings | ||||||
|     _loadSettings(); |     _loadSettings(); | ||||||
|  |  | ||||||
|     // Initially load the profile and attempt server connection |     // Initially load the profile and attempt server connection | ||||||
|     _loadProfile(); |     _loadProfile(); | ||||||
|  |  | ||||||
|  |     _refreshNotifications(); | ||||||
|  |  | ||||||
|  |     // Refresh notifications every ~30 seconds | ||||||
|  |     Timer.periodic( | ||||||
|  |         Duration( | ||||||
|  |           milliseconds: 30000, | ||||||
|  |         ), (timer) { | ||||||
|  |       _refreshNotifications(); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   // Index of bottom navigation bar | ||||||
|  |   int _tabIndex = 0; | ||||||
|  |  | ||||||
|  |   // Number of outstanding notifications | ||||||
|  |   int _notificationCounter = 0; | ||||||
|  |  | ||||||
|   bool homeShowPo = false; |   bool homeShowPo = false; | ||||||
|   bool homeShowSubscribed = false; |   bool homeShowSubscribed = false; | ||||||
|   bool homeShowManufacturers = false; |   bool homeShowManufacturers = false; | ||||||
| @@ -52,18 +71,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|   // Selected user profile |   // Selected user profile | ||||||
|   UserProfile? _profile; |   UserProfile? _profile; | ||||||
|  |  | ||||||
|   void _search(BuildContext context) { |  | ||||||
|     if (!InvenTreeAPI().checkConnection(context)) return; |  | ||||||
|  |  | ||||||
|     Navigator.push( |  | ||||||
|       context, |  | ||||||
|       MaterialPageRoute( |  | ||||||
|         builder: (context) => SearchWidget() |  | ||||||
|       ) |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   void _scan(BuildContext context) { |   void _scan(BuildContext context) { | ||||||
|     if (!InvenTreeAPI().checkConnection(context)) return; |     if (!InvenTreeAPI().checkConnection(context)) return; | ||||||
|  |  | ||||||
| @@ -168,6 +175,18 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|     setState(() {}); |     setState(() {}); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /* | ||||||
|  |    * Refresh the number of active notifications for this user | ||||||
|  |    */ | ||||||
|  |   Future<void> _refreshNotifications() async { | ||||||
|  |  | ||||||
|  |     final notifications = await InvenTreeNotification().list(); | ||||||
|  |  | ||||||
|  |     setState(() { | ||||||
|  |       _notificationCounter = notifications.length; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  | ||||||
|   Widget _listTile(BuildContext context, String label, IconData icon, {Function()? callback, String role = "", String permission = ""}) { |   Widget _listTile(BuildContext context, String label, IconData icon, {Function()? callback, String role = "", String permission = ""}) { | ||||||
|  |  | ||||||
| @@ -224,16 +243,6 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|       } |       } | ||||||
|     )); |     )); | ||||||
|  |  | ||||||
|     // Search widget |  | ||||||
|     tiles.add(_listTile( |  | ||||||
|       context, |  | ||||||
|       L10().search, |  | ||||||
|       FontAwesomeIcons.search, |  | ||||||
|       callback: () { |  | ||||||
|         _search(context); |  | ||||||
|       } |  | ||||||
|     )); |  | ||||||
|  |  | ||||||
|     // Parts |     // Parts | ||||||
|     tiles.add(_listTile( |     tiles.add(_listTile( | ||||||
|       context, |       context, | ||||||
| @@ -327,6 +336,79 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|     return tiles; |     return tiles; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /* | ||||||
|  |    * Return the main body widget for display. | ||||||
|  |    * This depends on the current value of _tabIndex | ||||||
|  |    */ | ||||||
|  |   Widget getBody(BuildContext context) { | ||||||
|  |     switch (_tabIndex) { | ||||||
|  |       case 1: // Search widget | ||||||
|  |         return SearchWidget(false); | ||||||
|  |       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.home), | ||||||
|  |         label: L10().home, | ||||||
|  |       ), | ||||||
|  |       BottomNavigationBarItem( | ||||||
|  |         icon: FaIcon(FontAwesomeIcons.search), | ||||||
|  |         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 | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |  | ||||||
| @@ -345,10 +427,18 @@ class _InvenTreeHomePageState extends State<InvenTreeHomePage> { | |||||||
|         ], |         ], | ||||||
|       ), |       ), | ||||||
|       drawer: InvenTreeDrawer(context), |       drawer: InvenTreeDrawer(context), | ||||||
|       body: ListView( |       body: getBody(context), | ||||||
|         scrollDirection: Axis.vertical, |       bottomNavigationBar: BottomNavigationBar( | ||||||
|         children: getListTiles(context), |         currentIndex: _tabIndex, | ||||||
|       ) |         onTap: (int index) { | ||||||
|  |           setState(() { | ||||||
|  |             _tabIndex = index; | ||||||
|  |           }); | ||||||
|  |  | ||||||
|  |           _refreshNotifications(); | ||||||
|  |         }, | ||||||
|  |         items: getNavBarItems(context), | ||||||
|  |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										100
									
								
								lib/widget/notifications.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								lib/widget/notifications.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  |  | ||||||
|  | import "package:flutter/material.dart"; | ||||||
|  |  | ||||||
|  | import "package:font_awesome_flutter/font_awesome_flutter.dart"; | ||||||
|  |  | ||||||
|  | import "package:inventree/l10.dart"; | ||||||
|  | import "package:inventree/inventree/model.dart"; | ||||||
|  | import "package:inventree/inventree/notification.dart"; | ||||||
|  | import "package:inventree/widget/refreshable_state.dart"; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class NotificationWidget extends StatefulWidget { | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   _NotificationState createState() => _NotificationState(); | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class _NotificationState extends RefreshableState<NotificationWidget> { | ||||||
|  |  | ||||||
|  |   _NotificationState() : super(); | ||||||
|  |  | ||||||
|  |   List<InvenTreeNotification> notifications = []; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AppBar? buildAppBar(BuildContext context) { | ||||||
|  |     // No app bar for the notification widget | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> request (BuildContext context) async { | ||||||
|  |  | ||||||
|  |     final results = await InvenTreeNotification().list(); | ||||||
|  |  | ||||||
|  |     notifications.clear(); | ||||||
|  |  | ||||||
|  |     for (InvenTreeModel n in results) { | ||||||
|  |       if (n is InvenTreeNotification) { | ||||||
|  |         notifications.add(n); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<void> dismissNotification(BuildContext context, InvenTreeNotification notification) async { | ||||||
|  |  | ||||||
|  |     await notification.dismiss(); | ||||||
|  |  | ||||||
|  |     refresh(context); | ||||||
|  |  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   List<Widget> renderNotifications(BuildContext context) { | ||||||
|  |  | ||||||
|  |     List<Widget> tiles = []; | ||||||
|  |  | ||||||
|  |     tiles.add( | ||||||
|  |       ListTile( | ||||||
|  |         title: Text( | ||||||
|  |           L10().notifications, | ||||||
|  |         ), | ||||||
|  |         subtitle: notifications.isEmpty ? Text(L10().notificationsEmpty) : null, | ||||||
|  |         leading: notifications.isEmpty ? FaIcon(FontAwesomeIcons.bellSlash) : FaIcon(FontAwesomeIcons.bell), | ||||||
|  |         trailing: Text("${notifications.length}"), | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     for (var notification in notifications) { | ||||||
|  |       tiles.add( | ||||||
|  |         ListTile( | ||||||
|  |           title: Text(notification.name), | ||||||
|  |           subtitle: Text(notification.message), | ||||||
|  |           trailing: IconButton( | ||||||
|  |             icon: FaIcon(FontAwesomeIcons.bookmark), | ||||||
|  |             onPressed: () async { | ||||||
|  |               dismissNotification(context, notification); | ||||||
|  |             }, | ||||||
|  |           ), | ||||||
|  |         ) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return tiles; | ||||||
|  |  | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Widget getBody(BuildContext context) { | ||||||
|  |     return Center( | ||||||
|  |       child: ListView( | ||||||
|  |         children: ListTile.divideTiles( | ||||||
|  |           context: context, | ||||||
|  |           tiles: renderNotifications(context), | ||||||
|  |         ).toList() | ||||||
|  |       ) | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -80,6 +80,14 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   AppBar? buildAppBar(BuildContext context) { | ||||||
|  |     return AppBar( | ||||||
|  |       title: Text(getAppBarTitle(context)), | ||||||
|  |       actions: getAppBarActions(context), | ||||||
|  |       leading: backButton(context, refreshableKey), | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |  | ||||||
| @@ -88,11 +96,7 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | |||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       key: refreshableKey, |       key: refreshableKey, | ||||||
|       appBar: AppBar( |       appBar: buildAppBar(context), | ||||||
|         title: Text(getAppBarTitle(context)), |  | ||||||
|         actions: getAppBarActions(context), |  | ||||||
|         leading: backButton(context, refreshableKey), |  | ||||||
|       ), |  | ||||||
|       drawer: getDrawer(context), |       drawer: getDrawer(context), | ||||||
|       floatingActionButton: getFab(context), |       floatingActionButton: getFab(context), | ||||||
|       body: Builder( |       body: Builder( | ||||||
|   | |||||||
| @@ -21,16 +21,33 @@ import "package:inventree/widget/location_list.dart"; | |||||||
| // Widget for performing database-wide search | // Widget for performing database-wide search | ||||||
| class SearchWidget extends StatefulWidget { | class SearchWidget extends StatefulWidget { | ||||||
|  |  | ||||||
|  |   const SearchWidget(this.hasAppbar); | ||||||
|  |  | ||||||
|  |   final bool hasAppbar; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   _SearchDisplayState createState() => _SearchDisplayState(); |   _SearchDisplayState createState() => _SearchDisplayState(hasAppbar); | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| class _SearchDisplayState extends RefreshableState<SearchWidget> { | class _SearchDisplayState extends RefreshableState<SearchWidget> { | ||||||
|  |  | ||||||
|  |   _SearchDisplayState(this.hasAppBar) : super(); | ||||||
|  |  | ||||||
|  |   final bool hasAppBar; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String getAppBarTitle(BuildContext context) => L10().search; |   String getAppBarTitle(BuildContext context) => L10().search; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   AppBar? buildAppBar(BuildContext context) { | ||||||
|  |     if (hasAppBar) { | ||||||
|  |       return super.buildAppBar(context); | ||||||
|  |     } else { | ||||||
|  |       return null; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   final TextEditingController searchController = TextEditingController(); |   final TextEditingController searchController = TextEditingController(); | ||||||
|  |  | ||||||
|   Timer? debounceTimer; |   Timer? debounceTimer; | ||||||
| @@ -155,12 +172,15 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> { | |||||||
|         child: ListTile( |         child: ListTile( | ||||||
|           title: TextField( |           title: TextField( | ||||||
|             readOnly: false, |             readOnly: false, | ||||||
|  |             decoration: InputDecoration( | ||||||
|  |               helperText: L10().queryEmpty, | ||||||
|  |             ), | ||||||
|             controller: searchController, |             controller: searchController, | ||||||
|             onChanged: (String text) { |             onChanged: (String text) { | ||||||
|               onSearchTextChanged(text); |               onSearchTextChanged(text); | ||||||
|             }, |             }, | ||||||
|           ), |           ), | ||||||
|           leading: IconButton( |           trailing: IconButton( | ||||||
|             icon: FaIcon(FontAwesomeIcons.backspace, color: Colors.red), |             icon: FaIcon(FontAwesomeIcons.backspace, color: Colors.red), | ||||||
|             onPressed: () { |             onPressed: () { | ||||||
|               searchController.clear(); |               searchController.clear(); | ||||||
| @@ -315,7 +335,7 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> { | |||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (results.isEmpty) { |     if (results.isEmpty && searchController.text.isNotEmpty) { | ||||||
|       tiles.add( |       tiles.add( | ||||||
|         ListTile( |         ListTile( | ||||||
|           title: Text(L10().queryNoResults), |           title: Text(L10().queryNoResults), | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ class _StockListState extends RefreshableState<StockItemList> { | |||||||
|   final Map<String, String> filters; |   final Map<String, String> filters; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String getAppBarTitle(BuildContext context) => L10().purchaseOrders; |   String getAppBarTitle(BuildContext context) => L10().stockItems; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget getBody(BuildContext context) { |   Widget getBody(BuildContext context) { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user