diff --git a/packages/smooth_app/lib/data_models/product_list.dart b/packages/smooth_app/lib/data_models/product_list.dart index c34af31a05b..6e155d6db98 100644 --- a/packages/smooth_app/lib/data_models/product_list.dart +++ b/packages/smooth_app/lib/data_models/product_list.dart @@ -255,5 +255,6 @@ class ProductList { } } + /// Can be edited or renamed bool get isEditable => listType == ProductListType.USER; } diff --git a/packages/smooth_app/lib/database/dao_product_list.dart b/packages/smooth_app/lib/database/dao_product_list.dart index 134408ed586..b851c657a83 100644 --- a/packages/smooth_app/lib/database/dao_product_list.dart +++ b/packages/smooth_app/lib/database/dao_product_list.dart @@ -168,6 +168,10 @@ class DaoProductList extends AbstractDao { productList.set(list.barcodes); } + /// Checks if a list exists in the database. + bool exist(final ProductList productList) => + _getBox().containsKey(getKey(productList)); + /// Returns the number of barcodes quickly but without product check. Future getLength(final ProductList productList) async { final _BarcodeList? list = await _get(productList); diff --git a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart index cb1b8ffe52c..1eab1795d6d 100644 --- a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart +++ b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_bottom_sheet.dart @@ -123,7 +123,9 @@ class SmoothModalSheetHeader extends StatelessWidget implements SizeWidget { title, maxLines: 1, overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.titleLarge, + style: Theme.of(context).textTheme.titleLarge?.copyWith( + fontWeight: FontWeight.bold, + ), ), ), ), diff --git a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart index f9029adb8cc..f1128ffdbc4 100644 --- a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart +++ b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet.dart @@ -47,44 +47,60 @@ class SmoothDraggableBottomSheetState @override Widget build(BuildContext context) { + final Color backgroundColor = widget.bottomSheetColor ?? + Theme.of(context).bottomSheetTheme.backgroundColor ?? + Theme.of(context).scaffoldBackgroundColor; + final double bottomPaddingHeight = MediaQuery.paddingOf(context).bottom; + return NotificationListener( onNotification: _scrolling, - child: SafeArea( - child: DraggableScrollableSheet( - minChildSize: 0.0, - maxChildSize: widget.maxHeightFraction, - initialChildSize: widget.initHeightFraction, - snap: true, - controller: _controller, - builder: (BuildContext context, ScrollController controller) { - return DecoratedBox( - decoration: BoxDecoration( - borderRadius: widget.borderRadius, - color: widget.bottomSheetColor ?? - Theme.of(context).bottomSheetTheme.backgroundColor ?? - Theme.of(context).scaffoldBackgroundColor, - ), - child: Material( - type: MaterialType.transparency, - child: ClipRRect( - borderRadius: widget.borderRadius, - child: _SmoothDraggableContent( - bodyBuilder: widget.bodyBuilder, - headerBuilder: widget.headerBuilder, - headerHeight: widget.headerHeight, - currentExtent: _controller.isAttached - ? _controller.size - : widget.initHeightFraction, - scrollController: controller, - cacheExtent: _calculateCacheExtent( - MediaQuery.of(context).viewInsets.bottom, + child: Column( + children: [ + Expanded( + child: SafeArea( + bottom: false, + child: DraggableScrollableSheet( + minChildSize: 0.0, + maxChildSize: widget.maxHeightFraction, + initialChildSize: widget.initHeightFraction, + snap: true, + controller: _controller, + builder: (BuildContext context, ScrollController controller) { + return DecoratedBox( + decoration: BoxDecoration( + borderRadius: widget.borderRadius, + color: backgroundColor, ), - ), - ), + child: Material( + type: MaterialType.transparency, + child: ClipRRect( + borderRadius: widget.borderRadius, + child: _SmoothDraggableContent( + bodyBuilder: widget.bodyBuilder, + headerBuilder: widget.headerBuilder, + headerHeight: widget.headerHeight, + currentExtent: _controller.isAttached + ? _controller.size + : widget.initHeightFraction, + scrollController: controller, + cacheExtent: _calculateCacheExtent( + MediaQuery.of(context).viewInsets.bottom, + ), + ), + ), + ), + ); + }, ), - ); - }, - ), + ), + ), + if (bottomPaddingHeight > 0) + SizedBox( + width: double.infinity, + height: bottomPaddingHeight, + child: ColoredBox(color: backgroundColor), + ), + ], ), ); } diff --git a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart index 0e4013482f6..7e327b175d1 100644 --- a/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart +++ b/packages/smooth_app/lib/generic_lib/bottom_sheets/smooth_draggable_bottom_sheet_route.dart @@ -81,6 +81,7 @@ class _FlexibleBottomSheetRoute extends PopupRoute { Animation secondaryAnimation, ) { final Widget bottomSheet = MediaQuery.removePadding( + removeBottom: false, context: context, child: SmoothDraggableBottomSheet( initHeightFraction: initHeight, diff --git a/packages/smooth_app/lib/pages/all_product_list_page.dart b/packages/smooth_app/lib/pages/all_product_list_page.dart index cf915da2394..e0ee8ff1fa3 100644 --- a/packages/smooth_app/lib/pages/all_product_list_page.dart +++ b/packages/smooth_app/lib/pages/all_product_list_page.dart @@ -6,6 +6,7 @@ import 'package:smooth_app/database/dao_product_list.dart'; import 'package:smooth_app/database/local_database.dart'; import 'package:smooth_app/generic_lib/design_constants.dart'; import 'package:smooth_app/pages/preferences/user_preferences_list_tile.dart'; +import 'package:smooth_app/pages/product/common/product_list_popup_items.dart'; import 'package:smooth_app/pages/product/common/product_query_page_helper.dart'; import 'package:smooth_app/pages/product_list_user_dialog_helper.dart'; @@ -43,62 +44,73 @@ class AllProductListModal extends StatelessWidget { final ProductList productList = productLists[index]; return Column( children: [ - UserPreferencesListTile( - title: Text( - ProductQueryPageHelper.getProductListLabel( - productList, - appLocalizations, - ), - ), - subtitle: FutureBuilder( - future: daoProductList.getLength(productList), - builder: ( - final BuildContext context, - final AsyncSnapshot snapshot, - ) { - if (snapshot.data != null) { - return Text( - appLocalizations.user_list_length(snapshot.data!), - ); - } - return EMPTY_WIDGET; - }, - ), - trailing: productList.isEditable - ? PopupMenuButton( - itemBuilder: (BuildContext context) { - return >[ - PopupMenuItem( - value: PopupMenuEntries.deleteList, - child: ListTile( - leading: const Icon(Icons.delete), - title: - Text(appLocalizations.action_delete_list), - contentPadding: EdgeInsets.zero, - ), - onTap: () { - WidgetsBinding.instance - .addPostFrameCallback((_) { - ProductListUserDialogHelper(daoProductList) - .showDeleteUserListDialog( - context, productList); - }); - }) - ]; - }, - icon: const Icon(Icons.more_vert), - ) - : null, - selected: productList.listType == currentList.listType && - productList.parameters == currentList.parameters, - selectedColor: Theme.of(context).primaryColor.withOpacity(0.2), - contentPadding: const EdgeInsetsDirectional.only( - start: VERY_LARGE_SPACE, - end: LARGE_SPACE, - top: VERY_SMALL_SPACE, - bottom: VERY_SMALL_SPACE, - ), - onTap: () => Navigator.of(context).pop(productList), + FutureBuilder( + future: daoProductList.getLength(productList), + builder: ( + final BuildContext context, + final AsyncSnapshot snapshot, + ) { + final int productsLength = snapshot.data ?? 0; + + return UserPreferencesListTile( + title: Text( + ProductQueryPageHelper.getProductListLabel( + productList, + appLocalizations, + ), + ), + subtitle: Text( + appLocalizations.user_list_length(productsLength), + ), + trailing: PopupMenuButton( + itemBuilder: (BuildContext context) { + return >[ + _shareMenu( + appLocalizations, + daoProductList, + localDatabase, + context, + productList, + ), + _openInWebMenu( + appLocalizations, + daoProductList, + localDatabase, + context, + productList, + ), + if (productsLength > 0) + _clearListMenu( + appLocalizations, + daoProductList, + localDatabase, + context, + productList, + ), + if (productList.isEditable) + _deleteListMenu( + appLocalizations, + daoProductList, + context, + productList, + ), + ]; + }, + icon: const Icon(Icons.more_vert), + ), + selected: productList.listType == currentList.listType && + productList.parameters == currentList.parameters, + selectedColor: + Theme.of(context).primaryColor.withOpacity(0.2), + contentPadding: const EdgeInsetsDirectional.only( + start: VERY_LARGE_SPACE, + end: LARGE_SPACE, + top: VERY_SMALL_SPACE, + bottom: VERY_SMALL_SPACE, + ), + onTap: () => Navigator.of(context).pop(productList), + ); + }, ), if (index < productLists.length - 1) const Divider(height: 1.0), ], @@ -107,6 +119,112 @@ class AllProductListModal extends StatelessWidget { ), ); } + + PopupMenuItem _shareMenu( + AppLocalizations appLocalizations, + DaoProductList daoProductList, + LocalDatabase localDatabase, + BuildContext context, + ProductList productList, + ) { + final ProductListPopupShare popupShare = ProductListPopupShare(); + return PopupMenuItem( + value: PopupMenuEntries.shareList, + child: ListTile( + leading: const Icon(Icons.share), + title: Text(popupShare.getTitle(appLocalizations)), + contentPadding: EdgeInsets.zero, + onTap: () { + Navigator.of(context).pop(); + popupShare.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + ); + }, + ), + ); + } + + PopupMenuItem _openInWebMenu( + AppLocalizations appLocalizations, + DaoProductList daoProductList, + LocalDatabase localDatabase, + BuildContext context, + ProductList productList, + ) { + final ProductListPopupOpenInWeb webItem = ProductListPopupOpenInWeb(); + return PopupMenuItem( + value: PopupMenuEntries.openListInBrowser, + child: ListTile( + leading: const Icon(Icons.public), + title: Text(webItem.getTitle(appLocalizations)), + contentPadding: EdgeInsets.zero, + onTap: () { + Navigator.of(context).pop(); + webItem.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + ); + }, + ), + ); + } + + PopupMenuItem _clearListMenu( + AppLocalizations appLocalizations, + DaoProductList daoProductList, + LocalDatabase localDatabase, + BuildContext context, + ProductList productList, + ) { + final ProductListPopupClear clearItem = ProductListPopupClear(); + return PopupMenuItem( + value: PopupMenuEntries.clearList, + child: ListTile( + leading: const Icon(Icons.delete_sweep), + title: Text(clearItem.getTitle(appLocalizations)), + contentPadding: EdgeInsets.zero, + onTap: () async { + Navigator.of(context).pop(); + + clearItem.doSomething( + productList: productList, + localDatabase: localDatabase, + context: context, + ); + }, + ), + ); + } + + PopupMenuItem _deleteListMenu( + AppLocalizations appLocalizations, + DaoProductList daoProductList, + BuildContext context, + ProductList productList, + ) { + return PopupMenuItem( + value: PopupMenuEntries.deleteList, + child: ListTile( + leading: const Icon(Icons.delete), + title: Text(appLocalizations.action_delete_list), + contentPadding: EdgeInsets.zero, + ), + onTap: () { + WidgetsBinding.instance.addPostFrameCallback((_) { + ProductListUserDialogHelper(daoProductList) + .showDeleteUserListDialog(context, productList); + }); + }); + } } -enum PopupMenuEntries { deleteList } +enum PopupMenuEntries { + shareList, + openListInBrowser, + renameList, + clearList, + deleteList, +} diff --git a/packages/smooth_app/lib/pages/product/common/product_list_modal.dart b/packages/smooth_app/lib/pages/product/common/product_list_modal.dart index 9ea648181be..05e74d95bd0 100644 --- a/packages/smooth_app/lib/pages/product/common/product_list_modal.dart +++ b/packages/smooth_app/lib/pages/product/common/product_list_modal.dart @@ -89,8 +89,20 @@ class _ProductListPageState extends State final ThemeData themeData = Theme.of(context); final AppLocalizations appLocalizations = AppLocalizations.of(context); refreshUpToDate(); + + /// If we were on a user list, but it has been deleted, we switch to history + if (!daoProductList.exist(productList) && + productList.listType == ProductListType.USER) { + WidgetsBinding.instance.addPostFrameCallback((_) => setState( + () => productList = ProductList.history(), + )); + + return EMPTY_WIDGET; + } + final List products = productList.getList(); final bool dismissible; + switch (productList.listType) { case ProductListType.SCAN_SESSION: case ProductListType.SCAN_HISTORY: diff --git a/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart b/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart index ed888082476..651858440b4 100644 --- a/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart +++ b/packages/smooth_app/lib/pages/product/common/product_list_popup_items.dart @@ -52,6 +52,7 @@ class ProductListPopupClear extends ProductListPopupItem { final bool? ok = await showDialog( context: context, builder: (BuildContext context) => SmoothAlertDialog( + title: getTitle(appLocalizations), body: Text( productList.listType == ProductListType.USER ? appLocalizations.confirm_clear_user_list(productList.parameters)