mirror of
				https://github.com/inventree/inventree-app.git
				synced 2025-10-31 21:35:42 +00:00 
			
		
		
		
	Major overhaul of "paginated list" widget class
- Simplify implementation - Create mixin class for code reuse - Allow custom app-bar - Allow custom ordering / sorting options - Improve code commenting / readability
This commit is contained in:
		| @@ -17,48 +17,6 @@ import "package:inventree/widget/refreshable_state.dart"; | ||||
|  | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Widget for displaying a list of BomItems for the specified 'parent' Part instance | ||||
|  */ | ||||
| class BomList extends StatefulWidget { | ||||
|  | ||||
|   const BomList(this.parent); | ||||
|  | ||||
|   final InvenTreePart parent; | ||||
|  | ||||
|   @override | ||||
|   _BomListState createState() => _BomListState(parent); | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class _BomListState extends PaginatedState<BomList> { | ||||
|  | ||||
|   _BomListState(this.parent); | ||||
|  | ||||
|   final InvenTreePart parent; | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle(BuildContext context) => L10().billOfMaterials; | ||||
|  | ||||
|   @override | ||||
|   String get prefix => "bom_"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> get orderingOptions => { | ||||
|     "quantity": L10().quantity, | ||||
|     "sub_part": L10().part, | ||||
|   }; | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return PaginatedBomList({ | ||||
|       "part": parent.pk.toString(), | ||||
|     }); | ||||
|   } | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Create a paginated widget displaying a list of BomItem objects | ||||
|  */ | ||||
| @@ -78,13 +36,23 @@ class PaginatedBomList extends StatefulWidget { | ||||
|  | ||||
| class _PaginatedBomListState extends PaginatedSearchState<PaginatedBomList> { | ||||
|  | ||||
|   _PaginatedBomListState(Map<String, String> filters, this.onTotalChanged) : super(filters); | ||||
|   _PaginatedBomListState(Map<String, String> filters, this.onTotalChanged) : super(filters, fullscreen: true); | ||||
|  | ||||
|   Function(int)? onTotalChanged; | ||||
|  | ||||
|   @override | ||||
|   String get prefix => "bom_"; | ||||
|  | ||||
|   @override | ||||
|   Map<String, String> get orderingOptions => { | ||||
|     "quantity": L10().quantity, | ||||
|     "sub_part": L10().part, | ||||
|   }; | ||||
|  | ||||
|  | ||||
|   @override | ||||
|   String getAppBarTitle(BuildContext context) => L10().billOfMaterials; | ||||
|  | ||||
|   @override | ||||
|   Future<InvenTreePageResponse?> requestPage(int limit, int offset, Map<String, String> params) async { | ||||
|  | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class _NotificationState extends RefreshableState<NotificationWidget> { | ||||
|   List<InvenTreeNotification> notifications = []; | ||||
|  | ||||
|   @override | ||||
|   AppBar? buildAppBar(BuildContext context) { | ||||
|   AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) { | ||||
|     // No app bar for the notification widget | ||||
|     return null; | ||||
|   } | ||||
|   | ||||
| @@ -14,32 +14,32 @@ import "package:inventree/widget/refreshable_state.dart"; | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Generic widget class for displaying a "paginated list". | ||||
|  * Provides some basic functionality for adjusting ordering and filtering options | ||||
|  * Generic stateful widget for displaying paginated data retrieved via the API | ||||
|  * | ||||
|  * - Can be displayed as "full screen" (with app-bar and drawer) | ||||
|  * - Can be displayed as a standalone widget | ||||
|  */ | ||||
| abstract class PaginatedState<T extends StatefulWidget> extends RefreshableState<T> { | ||||
| class PaginatedSearchState<T extends StatefulWidget> extends State<T> with BaseWidgetProperties { | ||||
|  | ||||
|   PaginatedSearchState(this.filters, {this.fullscreen = true}); | ||||
|  | ||||
|   final _key = GlobalKey<ScaffoldState>(); | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   static const _pageSize = 25; | ||||
|  | ||||
|   // Determine if this widget is shown "fullscreen" (i.e. with appbar) | ||||
|   final bool fullscreen; | ||||
|  | ||||
|   // Prefix for storing and loading pagination options | ||||
|   // Override in implementing class | ||||
|   String get prefix => "prefix_"; | ||||
|  | ||||
|   // Ordering options for this paginated state (override in implementing class) | ||||
|   // Return a map of sorting options available for this list | ||||
|   // Should be overridden by an implementing subclass | ||||
|   Map<String, String> get orderingOptions => {}; | ||||
|  | ||||
|   @override | ||||
|   List<Widget> getAppBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
|  | ||||
|     // If ordering options have been provided | ||||
|     if (orderingOptions.isNotEmpty) { | ||||
|       actions.add(IconButton( | ||||
|         icon: FaIcon(FontAwesomeIcons.sort), | ||||
|         onPressed: () => _updateFilters(context), | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
|   // Return the selected ordering "field" for this list widget | ||||
|   Future<String> orderingField() async { | ||||
|     dynamic field = await InvenTreeSettingsManager().getValue("${prefix}ordering_field", null); | ||||
| @@ -62,9 +62,21 @@ abstract class PaginatedState<T extends StatefulWidget> extends RefreshableState | ||||
|     return order == "+" ? "+" : "-"; | ||||
|   } | ||||
|  | ||||
|   // Update the (configurable) filters for this paginated list | ||||
|   Future<void> _updateFilters(BuildContext context) async { | ||||
|   // Return string for determining 'ordering' of paginated list | ||||
|   Future<String> get orderingString async { | ||||
|     dynamic field = await orderingField(); | ||||
|     dynamic order = await orderingOrder(); | ||||
|  | ||||
|     // Return an empty string if no field is provided | ||||
|     if (field.toString().isEmpty) { | ||||
|       return ""; | ||||
|     } | ||||
|  | ||||
|     return "${order}${field}"; | ||||
|   } | ||||
|  | ||||
|   // Update the (configurable) filters for this paginated list | ||||
|   Future<void> _saveOrderingOptions(BuildContext context) async { | ||||
|     // Retrieve stored setting | ||||
|     dynamic _field = await orderingField(); | ||||
|     dynamic _order = await orderingOrder(); | ||||
| @@ -96,12 +108,12 @@ abstract class PaginatedState<T extends StatefulWidget> extends RefreshableState | ||||
|         "value": _order, | ||||
|         "choices": [ | ||||
|           { | ||||
|             "value": "+", | ||||
|             "display_name": "Ascending", | ||||
|           "value": "+", | ||||
|           "display_name": "Ascending", | ||||
|           }, | ||||
|           { | ||||
|             "value": "-", | ||||
|             "display_name": "Descending", | ||||
|           "value": "-", | ||||
|           "display_name": "Descending", | ||||
|           } | ||||
|         ] | ||||
|       } | ||||
| @@ -123,27 +135,12 @@ abstract class PaginatedState<T extends StatefulWidget> extends RefreshableState | ||||
|         await InvenTreeSettingsManager().setValue("${prefix}ordering_field", f); | ||||
|         await InvenTreeSettingsManager().setValue("${prefix}ordering_order", o); | ||||
|  | ||||
|         // Refresh the widget | ||||
|         setState(() {}); | ||||
|         // Refresh data from the server | ||||
|         _pagingController.refresh(); | ||||
|       } | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class PaginatedSearchState<T extends StatefulWidget> extends State<T> { | ||||
|  | ||||
|   PaginatedSearchState(this.filters); | ||||
|  | ||||
|   final Map<String, String> filters; | ||||
|  | ||||
|   static const _pageSize = 25; | ||||
|  | ||||
|   // Prefix for storing and loading pagination options | ||||
|   // Override in implementing class | ||||
|   String get prefix => "prefix_"; | ||||
|  | ||||
|   // Search query term | ||||
|   String searchTerm = ""; | ||||
|  | ||||
| @@ -176,19 +173,12 @@ class PaginatedSearchState<T extends StatefulWidget> extends State<T> { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   Future<String> get ordering async { | ||||
|     dynamic field = await InvenTreeSettingsManager().getValue("${prefix}ordering_field", ""); | ||||
|     dynamic order = await InvenTreeSettingsManager().getValue("${prefix}ordering_order", "+"); | ||||
|  | ||||
|     return "${order}${field}"; | ||||
|   } | ||||
|  | ||||
|   Future<void> _fetchPage(int pageKey) async { | ||||
|     try { | ||||
|       Map<String, String> params = filters; | ||||
|  | ||||
|       params["search"] = "${searchTerm}"; | ||||
|       params["ordering"] = await ordering; | ||||
|       params["ordering"] = await orderingString; | ||||
|  | ||||
|       final page = await requestPage( | ||||
|         _pageSize, | ||||
| @@ -234,6 +224,8 @@ class PaginatedSearchState<T extends StatefulWidget> extends State<T> { | ||||
|     _pagingController.refresh(); | ||||
|   } | ||||
|  | ||||
|   // Function to construct a single paginated item | ||||
|   // Must be overridden in an implementing subclass | ||||
|   Widget buildItem(BuildContext context, InvenTreeModel item) { | ||||
|  | ||||
|     // This method must be overridden by the child class | ||||
| @@ -243,10 +235,31 @@ class PaginatedSearchState<T extends StatefulWidget> extends State<T> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   // Return a string which is displayed when there are no results | ||||
|   // Can be overridden by an implementing subclass | ||||
|   String get noResultsText => L10().noResults; | ||||
|  | ||||
|   @override | ||||
|   Widget build (BuildContext context) { | ||||
|  | ||||
|     if (fullscreen) { | ||||
|       return Scaffold( | ||||
|         key: _key, | ||||
|         appBar: buildAppBar(context, _key), | ||||
|         drawer: getDrawer(context), | ||||
|         body: Builder( | ||||
|           builder: (BuildContext ctx) { | ||||
|             return getBody(ctx); | ||||
|           } | ||||
|         ) | ||||
|       ); | ||||
|     } else { | ||||
|       return getBody(context); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget getBody(BuildContext context) { | ||||
|     return Column( | ||||
|         mainAxisAlignment: MainAxisAlignment.start, | ||||
|         children: [ | ||||
| @@ -277,6 +290,21 @@ class PaginatedSearchState<T extends StatefulWidget> extends State<T> { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   List<Widget> getAppBarActions(BuildContext context) { | ||||
|     List<Widget> actions = []; | ||||
|  | ||||
|     // If ordering options have been provided | ||||
|     if (orderingOptions.isNotEmpty) { | ||||
|       actions.add(IconButton( | ||||
|         icon: FaIcon(FontAwesomeIcons.sort), | ||||
|         onPressed: () => _saveOrderingOptions(context), | ||||
|       )); | ||||
|     } | ||||
|  | ||||
|     return actions; | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -354,7 +354,9 @@ class _PartDisplayState extends RefreshableState<PartDetailWidget> { | ||||
|                   Navigator.push( | ||||
|                     context, | ||||
|                     MaterialPageRoute( | ||||
|                       builder: (context) => BomList(part) | ||||
|                       builder: (context) => PaginatedBomList({ | ||||
|                         "part": part.pk.toString(), | ||||
|                       }) | ||||
|                     ) | ||||
|                   ); | ||||
|                 } | ||||
|   | ||||
| @@ -3,7 +3,53 @@ import "package:inventree/widget/drawer.dart"; | ||||
| import "package:flutter/material.dart"; | ||||
|  | ||||
|  | ||||
| abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | ||||
| /* | ||||
|  * Simple mixin class which defines simple methods for defining widget properties | ||||
|  */ | ||||
| mixin BaseWidgetProperties { | ||||
|  | ||||
|   // Return a list of appBar actions (default = None) | ||||
|   List<Widget> getAppBarActions(BuildContext context) { | ||||
|     return []; | ||||
|   } | ||||
|  | ||||
|   // Return a title for the appBar | ||||
|   String getAppBarTitle(BuildContext context) { return "--- app bar ---"; } | ||||
|  | ||||
|   // Function to construct a drawer (override if needed) | ||||
|   Widget getDrawer(BuildContext context) { | ||||
|     return InvenTreeDrawer(context); | ||||
|   } | ||||
|  | ||||
|   // Function to construct a body (MUST BE PROVIDED) | ||||
|   Widget getBody(BuildContext context) { | ||||
|  | ||||
|     // Default return is an empty ListView | ||||
|     return ListView(); | ||||
|   } | ||||
|  | ||||
|   Widget? getBottomNavBar(BuildContext context) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) { | ||||
|     return AppBar( | ||||
|       title: Text(getAppBarTitle(context)), | ||||
|       actions: getAppBarActions(context), | ||||
|       leading: backButton(context, key), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| /* | ||||
|  * Abstract base class which provides generic "refresh" functionality. | ||||
|  * | ||||
|  * - Drag down and release to 'refresh' the widget | ||||
|  * - Define some method which runs to 'refresh' the widget state | ||||
|  */ | ||||
| abstract class RefreshableState<T extends StatefulWidget> extends State<T> with BaseWidgetProperties { | ||||
|  | ||||
|   final refreshableKey = GlobalKey<ScaffoldState>(); | ||||
|  | ||||
| @@ -25,12 +71,6 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   List<Widget> getAppBarActions(BuildContext context) { | ||||
|     return []; | ||||
|   } | ||||
|  | ||||
|   String getAppBarTitle(BuildContext context) { return "App Bar Title"; } | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
| @@ -60,34 +100,6 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   // Function to construct a drawer (override if needed) | ||||
|   Widget getDrawer(BuildContext context) { | ||||
|     return InvenTreeDrawer(context); | ||||
|   } | ||||
|  | ||||
|   // Function to construct a body (MUST BE PROVIDED) | ||||
|   Widget getBody(BuildContext context) { | ||||
|  | ||||
|     // Default return is an empty ListView | ||||
|     return ListView(); | ||||
|   } | ||||
|  | ||||
|   Widget? getBottomNavBar(BuildContext context) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   Widget? getFab(BuildContext context) { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   AppBar? buildAppBar(BuildContext context) { | ||||
|     return AppBar( | ||||
|       title: Text(getAppBarTitle(context)), | ||||
|       actions: getAppBarActions(context), | ||||
|       leading: backButton(context, refreshableKey), | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|  | ||||
| @@ -96,9 +108,8 @@ abstract class RefreshableState<T extends StatefulWidget> extends State<T> { | ||||
|  | ||||
|     return Scaffold( | ||||
|       key: refreshableKey, | ||||
|       appBar: buildAppBar(context), | ||||
|       appBar: buildAppBar(context, refreshableKey), | ||||
|       drawer: getDrawer(context), | ||||
|       floatingActionButton: getFab(context), | ||||
|       body: Builder( | ||||
|         builder: (BuildContext context) { | ||||
|           return RefreshIndicator( | ||||
|   | ||||
| @@ -53,9 +53,9 @@ class _SearchDisplayState extends RefreshableState<SearchWidget> { | ||||
|   String getAppBarTitle(BuildContext context) => L10().search; | ||||
|  | ||||
|   @override | ||||
|   AppBar? buildAppBar(BuildContext context) { | ||||
|   AppBar? buildAppBar(BuildContext context, GlobalKey<ScaffoldState> key) { | ||||
|     if (hasAppBar) { | ||||
|       return super.buildAppBar(context); | ||||
|       return super.buildAppBar(context, key); | ||||
|     } else { | ||||
|       return null; | ||||
|     } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user