From 8de65a2083bd07657703c240fd8697a095cace1b Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 25 Apr 2024 20:52:33 +0700 Subject: [PATCH 01/13] TF-2646 Show banner select all emails when selection enabled --- .../thread/presentation/thread_view.dart | 23 +++++++ .../select_all_emails_banner_widget.dart | 64 +++++++++++++++++++ lib/l10n/intl_messages.arb | 18 ++++++ lib/main/localizations/app_localizations.dart | 16 +++++ 4 files changed, 121 insertions(+) create mode 100644 lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index a12a60f5aa..7a6f7289e6 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -11,12 +11,14 @@ import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart' import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_banner_widget.dart'; import 'package:tmail_ui_user/features/quotas/presentation/widget/quotas_banner_widget.dart'; +import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; @@ -35,6 +37,7 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_bu import 'package:tmail_ui_user/features/thread/presentation/widgets/empty_emails_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/scroll_to_top_button_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/spam_report_banner_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -163,6 +166,26 @@ class ThreadView extends GetWidget }), if (!controller.responsiveUtils.isDesktop(context)) _buildMarkAsMailboxReadLoading(context), + if (PlatformInfo.isWeb) + Obx(() { + final isSelectionEnabled = controller + .mailboxDashBoardController + .isSelectionEnabled(); + + if (isSelectionEnabled) { + return SelectAllEmailBannerWidget( + limitEmailsInPage: ThreadConstants.defaultLimit + .value + .toInt(), + folderName: controller.mailboxDashBoardController + .selectedMailbox + .value + ?.getDisplayName(context), + ); + } else { + return const SizedBox.shrink(); + } + }), Obx(() => ThreadViewLoadingBarWidget(viewState: controller.viewState.value)), Expanded( child: Container( diff --git a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart new file mode 100644 index 0000000000..d2622ecd90 --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart @@ -0,0 +1,64 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class SelectAllEmailBannerWidget extends StatelessWidget { + final int limitEmailsInPage; + final String? folderName; + + const SelectAllEmailBannerWidget({ + Key? key, + required this.limitEmailsInPage, + this.folderName, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + color: AppColor.colorBgDesktop, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + vertical: 12, + ), + alignment: Alignment.center, + child: Text.rich( + TextSpan( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontSize: 14, + ), + children: [ + TextSpan( + text: AppLocalizations.of(context).all, + ), + TextSpan( + text: ' $limitEmailsInPage ', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + TextSpan( + text: AppLocalizations.of(context).mailsOnThisPageAreSelected, + ), + TextSpan( + text: AppLocalizations.of(context).selectAllMailInMailbox( + limitEmailsInPage, + folderName ?? AppLocalizations.of(context).folder.toLowerCase(), + ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.primaryColor, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + recognizer: TapGestureRecognizer()..onTap = () {} + ) + ] + ) + ), + ); + } +} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 08575bb675..52f0384aad 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4205,5 +4205,23 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "mailsOnThisPageAreSelected": "mails on this page are selected. ", + "@mailsOnThisPageAreSelected": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "selectAllMailInMailbox": "Select all {total} mails in {folderName}", + "@selectAllMailInMailbox": { + "type": "text", + "placeholders_order": [ + "total", + "folderName" + ], + "placeholders": { + "total": {}, + "folderName": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 6984007288..333657db00 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4417,3 +4417,19 @@ class AppLocalizations { } } + + String get mailsOnThisPageAreSelected { + return Intl.message( + 'mails on this page are selected. ', + name: 'mailsOnThisPageAreSelected' + ); + } + + String selectAllMailInMailbox(int total, String folderName) { + return Intl.message( + 'Select all $total mails in $folderName', + name: 'selectAllMailInMailbox', + args: [total, folderName] + ); + } +} \ No newline at end of file From ae531c63bb49f97dd33934afeb981e708d35bbbc Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 26 Apr 2024 00:29:12 +0700 Subject: [PATCH 02/13] TF-2646 Perform action enable selection all email & clear selection --- .../mailbox_dashboard_controller.dart | 1 + .../mailbox_dashboard_view_web.dart | 5 +- .../widgets/top_bar_thread_selection.dart | 39 +++++------ .../presentation/thread_controller.dart | 5 ++ .../thread/presentation/thread_view.dart | 23 +++---- ...ge_select_all_email_in_mailbox_widget.dart | 56 ++++++++++++++++ .../message_select_email_in_page_widget.dart | 61 ++++++++++++++++++ .../select_all_emails_banner_widget.dart | 64 ------------------- .../select_all_emails_in_mailbox_banner.dart | 57 +++++++++++++++++ lib/l10n/intl_messages.arb | 16 +++++ lib/main/localizations/app_localizations.dart | 15 +++++ 11 files changed, 244 insertions(+), 98 deletions(-) create mode 100644 lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart create mode 100644 lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart delete mode 100644 lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart create mode 100644 lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 1bf8d4d8c2..d7cc94ce10 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -242,6 +242,7 @@ class MailboxDashBoardController extends ReloadableController final attachmentDraggableAppState = Rxn(); final isRecoveringDeletedMessage = RxBool(false); final localFileDraggableAppState = Rxn(); + final isSelectAllEmailsEnabled = RxBool(false); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index bef9b16a6a..b98dd473be 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -238,8 +238,9 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { return Padding( padding: const EdgeInsets.symmetric(vertical: 8.5, horizontal: 16), child: TopBarThreadSelection( - listEmailSelected, - controller.mapMailboxById, + listEmail: listEmailSelected, + mapMailbox: controller.mapMailboxById, + isSelectAllEmailsEnabled: controller.isSelectAllEmailsEnabled.value, onCancelSelection: () => controller.dispatchAction(CancelSelectionAllEmailAction()), onEmailActionTypeAction: (listEmails, actionType) => diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index 82270ce269..3c6d6f4004 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -21,16 +21,16 @@ class TopBarThreadSelection extends StatelessWidget{ final Map mapMailbox; final OnEmailActionTypeAction? onEmailActionTypeAction; final VoidCallback? onCancelSelection; + final bool isSelectAllEmailsEnabled; - TopBarThreadSelection( - this.listEmail, - this.mapMailbox, - { - super.key, - this.onEmailActionTypeAction, - this.onCancelSelection, - } - ); + TopBarThreadSelection({ + super.key, + required this.listEmail, + required this.mapMailbox, + required this.isSelectAllEmailsEnabled, + this.onEmailActionTypeAction, + this.onCancelSelection, + }); @override Widget build(BuildContext context) { @@ -49,15 +49,18 @@ class TopBarThreadSelection extends StatelessWidget{ padding: const EdgeInsets.all(3), onTapActionCallback: onCancelSelection ), - Text( - AppLocalizations.of(context).count_email_selected(listEmail.length), - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: AppColor.colorTextButton - ) - ), - const SizedBox(width: 30), + if (!isSelectAllEmailsEnabled) + Padding( + padding: const EdgeInsetsDirectional.only(end: 30), + child: Text( + AppLocalizations.of(context).count_email_selected(listEmail.length), + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: AppColor.colorTextButton + ) + ), + ), TMailButtonWidget.fromIcon( icon: listEmail.isAllEmailRead ? imagePaths.icUnread diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index e6ad7cedae..5c52e59e19 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -770,11 +770,16 @@ class ThreadController extends BaseController with EmailActionController { final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) .toList(); + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); } + void enableSelectAllEmails() { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = true; + } + void closeFilterMessageActionSheet() { popBack(); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 7a6f7289e6..e679c96100 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -37,7 +37,7 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_bu import 'package:tmail_ui_user/features/thread/presentation/widgets/empty_emails_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/filter_message_cupertino_action_sheet_action_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/scroll_to_top_button_widget.dart'; -import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/spam_report_banner_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/thread_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -168,19 +168,14 @@ class ThreadView extends GetWidget _buildMarkAsMailboxReadLoading(context), if (PlatformInfo.isWeb) Obx(() { - final isSelectionEnabled = controller - .mailboxDashBoardController - .isSelectionEnabled(); - - if (isSelectionEnabled) { - return SelectAllEmailBannerWidget( - limitEmailsInPage: ThreadConstants.defaultLimit - .value - .toInt(), - folderName: controller.mailboxDashBoardController - .selectedMailbox - .value - ?.getDisplayName(context), + if (controller.mailboxDashBoardController.isSelectionEnabled()) { + return SelectAllEmailInMailboxBanner( + limitEmailsInPage: ThreadConstants.defaultLimit.value.toInt(), + totalEmails: controller.mailboxDashBoardController.selectedMailbox.value?.totalEmails?.value.value.toInt() ?? 0, + folderName: controller.mailboxDashBoardController.selectedMailbox.value?.getDisplayName(context) + ?? AppLocalizations.of(context).folder.toLowerCase(), + onSelectAllEmailAction: controller.enableSelectAllEmails, + onClearSelection: controller.cancelSelectEmail ); } else { return const SizedBox.shrink(); diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart new file mode 100644 index 0000000000..0b0390dd3a --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart @@ -0,0 +1,56 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class MessageSelectAllEmailInMailboxWidget extends StatelessWidget { + + final int totalEmails; + final String folderName; + final VoidCallback onClearSelection; + + const MessageSelectAllEmailInMailboxWidget({ + super.key, + required this.totalEmails, + required this.folderName, + required this.onClearSelection + }); + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontSize: 14, + ), + children: [ + TextSpan( + text: AppLocalizations.of(context).all, + ), + TextSpan( + text: ' $totalEmails ', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + TextSpan( + text: AppLocalizations.of(context).mailsInMailboxAreSelected(folderName), + ), + TextSpan( + text: AppLocalizations.of(context).clearSelection, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.primaryColor, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + recognizer: TapGestureRecognizer()..onTap = onClearSelection + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart new file mode 100644 index 0000000000..db6f4180cd --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart @@ -0,0 +1,61 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class MessageSelectEmailInPageWidget extends StatelessWidget { + + final int limitEmailsInPage; + final int totalEmails; + final String folderName; + final VoidCallback onSelectAllEmailAction; + + const MessageSelectEmailInPageWidget({ + super.key, + required this.limitEmailsInPage, + required this.totalEmails, + required this.folderName, + required this.onSelectAllEmailAction, + }); + + @override + Widget build(BuildContext context) { + return Text.rich( + TextSpan( + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontSize: 14, + ), + children: [ + TextSpan( + text: AppLocalizations.of(context).all, + ), + TextSpan( + text: ' $limitEmailsInPage ', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + ), + TextSpan( + text: AppLocalizations.of(context).mailsOnThisPageAreSelected, + ), + TextSpan( + text: AppLocalizations.of(context).selectAllMailInMailbox( + totalEmails, + folderName, + ), + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: AppColor.primaryColor, + fontWeight: FontWeight.w500, + fontSize: 14, + ), + recognizer: TapGestureRecognizer()..onTap = onSelectAllEmailAction + ) + ] + ) + ); + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart deleted file mode 100644 index d2622ecd90..0000000000 --- a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_banner_widget.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:core/presentation/extensions/color_extension.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; -import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; - -class SelectAllEmailBannerWidget extends StatelessWidget { - final int limitEmailsInPage; - final String? folderName; - - const SelectAllEmailBannerWidget({ - Key? key, - required this.limitEmailsInPage, - this.folderName, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Container( - width: double.infinity, - color: AppColor.colorBgDesktop, - padding: const EdgeInsetsDirectional.symmetric( - horizontal: 16, - vertical: 12, - ), - alignment: Alignment.center, - child: Text.rich( - TextSpan( - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.black, - fontSize: 14, - ), - children: [ - TextSpan( - text: AppLocalizations.of(context).all, - ), - TextSpan( - text: ' $limitEmailsInPage ', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - TextSpan( - text: AppLocalizations.of(context).mailsOnThisPageAreSelected, - ), - TextSpan( - text: AppLocalizations.of(context).selectAllMailInMailbox( - limitEmailsInPage, - folderName ?? AppLocalizations.of(context).folder.toLowerCase(), - ), - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: AppColor.primaryColor, - fontWeight: FontWeight.w500, - fontSize: 14, - ), - recognizer: TapGestureRecognizer()..onTap = () {} - ) - ] - ) - ), - ); - } -} diff --git a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart new file mode 100644 index 0000000000..3b6c7cad3e --- /dev/null +++ b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart @@ -0,0 +1,57 @@ +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart'; + +class SelectAllEmailInMailboxBanner extends StatefulWidget { + final int limitEmailsInPage; + final int totalEmails; + final String folderName; + final VoidCallback onSelectAllEmailAction; + final VoidCallback onClearSelection; + + const SelectAllEmailInMailboxBanner({ + Key? key, + required this.limitEmailsInPage, + required this.totalEmails, + required this.folderName, + required this.onSelectAllEmailAction, + required this.onClearSelection, + }) : super(key: key); + + @override + State createState() => _SelectAllEmailInMailboxBannerState(); +} + +class _SelectAllEmailInMailboxBannerState extends State { + + bool _isSelectAllEmailsEnabled = false; + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + color: AppColor.colorBgDesktop, + padding: const EdgeInsetsDirectional.symmetric( + horizontal: 16, + vertical: 12, + ), + alignment: Alignment.center, + child: _isSelectAllEmailsEnabled + ? MessageSelectAllEmailInMailboxWidget( + totalEmails: widget.totalEmails, + folderName: widget.folderName, + onClearSelection: widget.onClearSelection, + ) + : MessageSelectEmailInPageWidget( + limitEmailsInPage: widget.limitEmailsInPage, + totalEmails: widget.totalEmails, + folderName: widget.folderName, + onSelectAllEmailAction: () { + widget.onSelectAllEmailAction(); + setState(() => _isSelectAllEmailsEnabled = true); + }, + ) + ); + } +} diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 52f0384aad..7d29adb9e8 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4223,5 +4223,21 @@ "total": {}, "folderName": {} } + }, + "mailsInMailboxAreSelected": "mails in {folderName} are selected. ", + "@mailsInMailboxAreSelected": { + "type": "text", + "placeholders_order": [ + "folderName" + ], + "placeholders": { + "folderName": {} + } + }, + "clearSelection": "Clear selection", + "@clearSelection": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 333657db00..397d23d3ec 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4432,4 +4432,19 @@ class AppLocalizations { args: [total, folderName] ); } + + String mailsInMailboxAreSelected(String folderName) { + return Intl.message( + 'mails in $folderName are selected. ', + name: 'mailsInMailboxAreSelected', + args: [folderName] + ); + } + + String get clearSelection { + return Intl.message( + 'Clear selection', + name: 'clearSelection', + ); + } } \ No newline at end of file From 2f7b09f7794656f07dd9be8b1e3b640e6a35bc39 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 26 Apr 2024 00:40:05 +0700 Subject: [PATCH 03/13] TF-2646 Validate to show selection emails banner --- .../thread/presentation/thread_controller.dart | 10 ++++++++++ lib/features/thread/presentation/thread_view.dart | 12 +++++------- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 5c52e59e19..6b603063ff 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1349,4 +1349,14 @@ class ThreadController extends BaseController with EmailActionController { _loadMoreEmails(); } } + + bool validateToShowSelectionEmailsBanner() { + return mailboxDashBoardController.isSelectionEnabled() && + mailboxDashBoardController.selectedMailbox.value != null && + mailboxDashBoardController.selectedMailbox.value!.totalEmails != null && + mailboxDashBoardController.listEmailSelected.length < + mailboxDashBoardController + .selectedMailbox.value!.totalEmails!.value.value + .toInt(); + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index e679c96100..61b7743896 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -18,7 +18,6 @@ import 'package:tmail_ui_user/features/manage_account/presentation/extensions/va import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_banner_widget.dart'; import 'package:tmail_ui_user/features/quotas/presentation/widget/quotas_banner_widget.dart'; -import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; @@ -166,14 +165,13 @@ class ThreadView extends GetWidget }), if (!controller.responsiveUtils.isDesktop(context)) _buildMarkAsMailboxReadLoading(context), - if (PlatformInfo.isWeb) + if (controller.responsiveUtils.isWebDesktop(context)) Obx(() { - if (controller.mailboxDashBoardController.isSelectionEnabled()) { + if (controller.validateToShowSelectionEmailsBanner()) { return SelectAllEmailInMailboxBanner( - limitEmailsInPage: ThreadConstants.defaultLimit.value.toInt(), - totalEmails: controller.mailboxDashBoardController.selectedMailbox.value?.totalEmails?.value.value.toInt() ?? 0, - folderName: controller.mailboxDashBoardController.selectedMailbox.value?.getDisplayName(context) - ?? AppLocalizations.of(context).folder.toLowerCase(), + limitEmailsInPage: controller.mailboxDashBoardController.listEmailSelected.length, + totalEmails: controller.mailboxDashBoardController.selectedMailbox.value!.totalEmails!.value.value.toInt(), + folderName: controller.mailboxDashBoardController.selectedMailbox.value!.getDisplayName(context), onSelectAllEmailAction: controller.enableSelectAllEmails, onClearSelection: controller.cancelSelectEmail ); From afb02c33542d5fda5c920219d39047b8c4804155 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 26 Apr 2024 01:04:52 +0700 Subject: [PATCH 04/13] TF-2646 Show more button & popup menu more action --- .../email_action_type_extension.dart | 12 +++++++ .../presentation/action/dashboard_action.dart | 14 +++++++- .../mailbox_dashboard_view_web.dart | 1 + .../widgets/top_bar_thread_selection.dart | 13 +++++++ .../presentation/thread_controller.dart | 36 ++++++++++++++++++- 5 files changed, 74 insertions(+), 2 deletions(-) diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index aaa4341fb8..70e37a9858 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -142,6 +142,12 @@ extension EmailActionTypeExtension on EmailActionType { return imagePaths.icMailboxArchived; case EmailActionType.downloadMessageAsEML: return imagePaths.icDownloadAttachment; + case EmailActionType.markAsRead: + return imagePaths.icRead; + case EmailActionType.moveToMailbox: + return imagePaths.icMove; + case EmailActionType.moveToTrash: + return imagePaths.icDeleteComposer; default: return ''; } @@ -163,6 +169,12 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).archiveMessage; case EmailActionType.downloadMessageAsEML: return AppLocalizations.of(context).downloadMessageAsEML; + case EmailActionType.markAsRead: + return AppLocalizations.of(context).mark_as_read; + case EmailActionType.moveToMailbox: + return AppLocalizations.of(context).move; + case EmailActionType.moveToTrash: + return AppLocalizations.of(context).move_to_trash; default: return ''; } diff --git a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart index 2ba5d48bc7..5359452a29 100644 --- a/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart +++ b/lib/features/mailbox_dashboard/presentation/action/dashboard_action.dart @@ -1,4 +1,5 @@ +import 'package:flutter/material.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/email/email_action_type.dart'; @@ -147,4 +148,15 @@ class OpenAdvancedSearchViewAction extends DashBoardAction {} class ClearSearchFilterAppliedAction extends DashBoardAction {} -class ClearAdvancedSearchFilterEmailAction extends DashBoardAction {} \ No newline at end of file +class ClearAdvancedSearchFilterEmailAction extends DashBoardAction {} + +class MoreSelectedEmailAction extends DashBoardAction { + + final BuildContext context; + final RelativeRect position; + + MoreSelectedEmailAction(this.context, this.position); + + @override + List get props => [context, position]; +} \ No newline at end of file diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index b98dd473be..b09f43e8a6 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -248,6 +248,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { listEmails, actionType )), + onMoreSelectedEmailAction: (position) => controller.dispatchAction(MoreSelectedEmailAction(context, position)), ), ); } else { diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index 3c6d6f4004..ebe5a7d76a 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -12,6 +12,7 @@ import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; typedef OnEmailActionTypeAction = Function(List listEmail, EmailActionType actionType); +typedef OnMoreSelectedEmailAction = Function(RelativeRect position); class TopBarThreadSelection extends StatelessWidget{ @@ -22,6 +23,7 @@ class TopBarThreadSelection extends StatelessWidget{ final OnEmailActionTypeAction? onEmailActionTypeAction; final VoidCallback? onCancelSelection; final bool isSelectAllEmailsEnabled; + final OnMoreSelectedEmailAction? onMoreSelectedEmailAction; TopBarThreadSelection({ super.key, @@ -30,6 +32,7 @@ class TopBarThreadSelection extends StatelessWidget{ required this.isSelectAllEmailsEnabled, this.onEmailActionTypeAction, this.onCancelSelection, + this.onMoreSelectedEmailAction, }); @override @@ -148,6 +151,16 @@ class TopBarThreadSelection extends StatelessWidget{ } } ), + const Spacer(), + if (isSelectAllEmailsEnabled) + TMailButtonWidget.fromIcon( + icon: imagePaths.icMoreVertical, + iconSize: 22, + iconColor: AppColor.primaryColor, + tooltipMessage: AppLocalizations.of(context).more, + backgroundColor: Colors.transparent, + onTapActionAtPositionCallback: onMoreSelectedEmailAction + ), ]); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 6b603063ff..209d2ddc50 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; @@ -18,9 +19,11 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; @@ -86,7 +89,7 @@ import 'package:universal_html/html.dart' as html; typedef StartRangeSelection = int; typedef EndRangeSelection = int; -class ThreadController extends BaseController with EmailActionController { +class ThreadController extends BaseController with EmailActionController, PopupMenuWidgetMixin { final networkConnectionController = Get.find(); @@ -328,6 +331,9 @@ class ThreadController extends BaseController with EmailActionController { } canSearchMore = true; mailboxDashBoardController.emailsInCurrentMailbox.clear(); + } else if (action is MoreSelectedEmailAction) { + showPopupMenuSelectionEmailAction(action.context, action.position); + mailboxDashBoardController.clearDashBoardAction(); } }); @@ -1359,4 +1365,32 @@ class ThreadController extends BaseController with EmailActionController { .selectedMailbox.value!.totalEmails!.value.value .toInt(); } + + void showPopupMenuSelectionEmailAction(BuildContext context, RelativeRect position) { + final listSelectionEmailActions = [ + EmailActionType.markAsRead, + EmailActionType.markAsUnread, + EmailActionType.moveToMailbox, + EmailActionType.moveToTrash, + ]; + + openPopupMenuAction( + context, + position, + listSelectionEmailActions.map((action) => PopupMenuItem( + padding: EdgeInsets.zero, + child: popupItem( + action.getIcon(imagePaths), + action.getTitle(context), + colorIcon: AppColor.colorTextButton, + styleName: const TextStyle( + fontWeight: FontWeight.w500, + fontSize: 14, + color: Colors.black + ), + onCallbackAction: () {} + ) + )).toList() + ); + } } \ No newline at end of file From a8dd2f56b0c75c2b4a12f1cfb83a5db0201aab9c Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 26 Apr 2024 11:15:04 +0700 Subject: [PATCH 05/13] TF-2646 Show confirm dialog when make to bulk action with selection emails --- .../presentation/thread_controller.dart | 43 ++++++++++++++++++- lib/l10n/intl_messages.arb | 24 +++++++++++ lib/main/localizations/app_localizations.dart | 20 +++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 209d2ddc50..d5b6b5e075 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -8,6 +8,7 @@ import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; @@ -36,6 +37,7 @@ import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; @@ -289,7 +291,14 @@ class ThreadController extends BaseController with EmailActionController, PopupM filterMessagesAction(action.option); mailboxDashBoardController.clearDashBoardAction(); } else if (action is HandleEmailActionTypeAction) { - pressEmailSelectionAction(action.emailAction, action.listEmailSelected); + if (_validateToShowConfirmBulkActionEmailsDialog()) { + _showConfirmDialogWhenMakeToActionForSelectionAllEmails(); + } else { + pressEmailSelectionAction( + action.emailAction, + action.listEmailSelected, + ); + } mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailDetailedFromSuggestionQuickSearchAction) { final mailboxContain = action.presentationEmail.findMailboxContain(mailboxDashBoardController.mapMailboxById); @@ -1388,9 +1397,39 @@ class ThreadController extends BaseController with EmailActionController, PopupM fontSize: 14, color: Colors.black ), - onCallbackAction: () {} + onCallbackAction: () { + popBack(); + if (!isSearchActive) { + _showConfirmDialogWhenMakeToActionForSelectionAllEmails(); + } + } ) )).toList() ); } + + bool _validateToShowConfirmBulkActionEmailsDialog() { + return mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue; + } + + Future _showConfirmDialogWhenMakeToActionForSelectionAllEmails() async { + final selectedMailbox = mailboxDashBoardController.selectedMailbox.value; + + if (currentContext == null || selectedMailbox == null) return; + + final appLocalizations = AppLocalizations.of(currentContext!); + final totalEmails = selectedMailbox.totalEmails?.value.value.toInt() ?? 0; + final folderName = selectedMailbox.getDisplayName(currentContext!); + + await showConfirmDialogAction( + currentContext!, + appLocalizations.messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox(totalEmails, folderName), + appLocalizations.ok, + title: appLocalizations.confirmBulkAction, + icon: SvgPicture.asset( + imagePaths.icQuotasWarning, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ), + ); + } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 7d29adb9e8..c3e9561bc9 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4239,5 +4239,29 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "confirmBulkAction": "Confirm bulk action", + "@confirmBulkAction": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "ok": "OK", + "@ok": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox": "This action will affect all {total} mails in {folderName}. Are you sure you want to continue?", + "@messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox": { + "type": "text", + "placeholders_order": [ + "total", + "folderName" + ], + "placeholders": { + "total": {}, + "folderName": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 397d23d3ec..4685edcfc4 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4447,4 +4447,24 @@ class AppLocalizations { name: 'clearSelection', ); } + + String get confirmBulkAction { + return Intl.message( + 'Confirm bulk action', + name: 'confirmBulkAction'); + } + + String get ok { + return Intl.message( + 'OK', + name: 'ok'); + } + + String messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox(int total, String folderName) { + return Intl.message( + 'This action will affect all $total mails in $folderName. Are you sure you want to continue?', + name: 'messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox', + args: [total, folderName] + ); + } } \ No newline at end of file From d4e6053286abb22bbb97162f7c157e00aafa84f2 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 09:46:53 +0700 Subject: [PATCH 06/13] TF-2646 Handle mark as all read for selection emails --- .../email_action_type_extension.dart | 3 ++ .../email_datasource_impl.dart | 2 +- .../email/data/network/email_api.dart | 37 +++++++++++++++- .../data/datasource/mailbox_datasource.dart | 2 +- .../mailbox_cache_datasource_impl.dart | 2 +- .../mailbox_datasource_impl.dart | 2 +- .../data/network/mailbox_isolate_worker.dart | 30 ++++++------- .../repository/mailbox_repository_impl.dart | 2 +- .../domain/repository/mailbox_repository.dart | 2 +- .../state/mark_as_mailbox_read_state.dart | 2 +- .../mark_as_mailbox_read_interactor.dart | 8 ++-- .../widgets/top_bar_thread_selection.dart | 40 ++++++++++++++--- .../presentation/thread_controller.dart | 43 +++++++++++++++++-- model/lib/email/email_action_type.dart | 3 +- 14 files changed, 138 insertions(+), 40 deletions(-) diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 70e37a9858..4a4ee610e9 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -143,6 +143,7 @@ extension EmailActionTypeExtension on EmailActionType { case EmailActionType.downloadMessageAsEML: return imagePaths.icDownloadAttachment; case EmailActionType.markAsRead: + case EmailActionType.markAllAsRead: return imagePaths.icRead; case EmailActionType.moveToMailbox: return imagePaths.icMove; @@ -175,6 +176,8 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).move; case EmailActionType.moveToTrash: return AppLocalizations.of(context).move_to_trash; + case EmailActionType.markAllAsRead: + return AppLocalizations.of(context).mark_all_as_read; default: return ''; } diff --git a/lib/features/email/data/datasource_impl/email_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_datasource_impl.dart index c64e3ea668..7ae59ccea5 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -82,7 +82,7 @@ class EmailDataSourceImpl extends EmailDataSource { ReadActions readActions ) { return Future.sync(() async { - return await emailAPI.markAsRead(session, accountId, emails, readActions); + return await emailAPI.markAsReadAndGetResult(session, accountId, emails, readActions); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 4ff1280058..7f0c5cd65c 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -239,7 +239,7 @@ class EmailAPI with HandleSetErrorMixin { } } - Future> markAsRead( + Future> markAsReadAndGetResult( Session session, AccountId accountId, List emails, @@ -277,6 +277,41 @@ class EmailAPI with HandleSetErrorMixin { }); } + Future> markAsRead( + Session session, + AccountId accountId, + List emails, + ReadActions readActions + ) async { + final setEmailMethod = SetEmailMethod(accountId) + ..addUpdates(emails.listEmailIds.generateMapUpdateObjectMarkAsRead(readActions)); + + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final response = await (requestBuilder..usings(capabilities)) + .build() + .execute(); + + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize + ); + + final listIdUpdated = setEmailResponse?.updated?.keys.toList(); + final mapErrors = handleSetResponse([setEmailResponse]); + + if (listIdUpdated != null && mapErrors.isEmpty) { + final listEmailIdUpdated = listIdUpdated.map((id) => EmailId(id)).toList(); + return listEmailIdUpdated; + } else { + throw SetMethodException(mapErrors); + } + } + Future> downloadAttachments( List attachments, AccountId accountId, diff --git a/lib/features/mailbox/data/datasource/mailbox_datasource.dart b/lib/features/mailbox/data/datasource/mailbox_datasource.dart index dd781a6185..5a70c5b6e6 100644 --- a/lib/features/mailbox/data/datasource/mailbox_datasource.dart +++ b/lib/features/mailbox/data/datasource/mailbox_datasource.dart @@ -39,7 +39,7 @@ abstract class MailboxDataSource { Future moveMailbox(Session session, AccountId accountId, MoveMailboxRequest request); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart index 03cf6c6aff..e863818945 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart @@ -79,7 +79,7 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart index 1dbb9c9f4d..2464f76bdf 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart @@ -88,7 +88,7 @@ class MailboxDataSourceImpl extends MailboxDataSource { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/data/network/mailbox_isolate_worker.dart b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart index 58a3306f1f..04991d12a9 100644 --- a/lib/features/mailbox/data/network/mailbox_isolate_worker.dart +++ b/lib/features/mailbox/data/network/mailbox_isolate_worker.dart @@ -37,7 +37,7 @@ class MailboxIsolateWorker { MailboxIsolateWorker(this._threadApi, this._emailApi, this._isolateExecutor); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, @@ -68,7 +68,7 @@ class MailboxIsolateWorker { ), fun1: _handleMarkAsMailboxReadAction, notification: (value) { - if (value is List) { + if (value is List) { log('MailboxIsolateWorker::markAsMailboxRead(): onUpdateProgress: PERCENT ${value.length / totalEmailUnread}'); onProgressController.add(Right(UpdatingMarkAsMailboxReadState( mailboxId: mailboxId, @@ -80,7 +80,7 @@ class MailboxIsolateWorker { } } - static Future> _handleMarkAsMailboxReadAction( + static Future> _handleMarkAsMailboxReadAction( MailboxMarkAsReadArguments args, TypeSendPort sendPort ) async { @@ -88,7 +88,7 @@ class MailboxIsolateWorker { BackgroundIsolateBinaryMessenger.ensureInitialized(rootIsolateToken); await HiveCacheConfig.instance.setUp(); - List emailListCompleted = List.empty(growable: true); + List emailIdListCompleted = List.empty(growable: true); try { bool mailboxHasEmails = true; UTCDate? lastReceivedDate; @@ -109,7 +109,6 @@ class MailboxIsolateWorker { ..setIsAscending(false)), properties: Properties({ EmailProperty.id, - EmailProperty.keywords, EmailProperty.receivedAt, })) .then((response) { @@ -138,25 +137,25 @@ class MailboxIsolateWorker { ReadActions.markAsRead); log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): MARK_READ: ${result.length}'); - emailListCompleted.addAll(result); - sendPort.send(emailListCompleted); + emailIdListCompleted.addAll(result); + sendPort.send(emailIdListCompleted); } } } catch (e) { log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): ERROR: $e'); } - log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailListCompleted.length}'); - return emailListCompleted; + log('MailboxIsolateWorker::_handleMarkAsMailboxRead(): TOTAL_READ: ${emailIdListCompleted.length}'); + return emailIdListCompleted; } - Future> _handleMarkAsMailboxReadActionOnWeb( + Future> _handleMarkAsMailboxReadActionOnWeb( Session session, AccountId accountId, MailboxId mailboxId, int totalEmailUnread, StreamController> onProgressController ) async { - List emailListCompleted = List.empty(growable: true); + List emailIdListCompleted = List.empty(growable: true); try { bool mailboxHasEmails = true; UTCDate? lastReceivedDate; @@ -177,7 +176,6 @@ class MailboxIsolateWorker { ..setIsAscending(false)), properties: Properties({ EmailProperty.id, - EmailProperty.keywords, EmailProperty.receivedAt, }) ).then((response) { @@ -201,18 +199,18 @@ class MailboxIsolateWorker { final result = await _emailApi.markAsRead(session, accountId, listEmailUnread, ReadActions.markAsRead); log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): MARK_READ: ${result.length}'); - emailListCompleted.addAll(result); + emailIdListCompleted.addAll(result); onProgressController.add(Right(UpdatingMarkAsMailboxReadState( mailboxId: mailboxId, totalUnread: totalEmailUnread, - countRead: emailListCompleted.length))); + countRead: emailIdListCompleted.length))); } } } catch (e) { log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): ERROR: $e'); } - log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): TOTAL_READ: ${emailListCompleted.length}'); - return emailListCompleted; + log('MailboxIsolateWorker::_handleMarkAsMailboxReadActionOnWeb(): TOTAL_READ: ${emailIdListCompleted.length}'); + return emailIdListCompleted; } } diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index 8e28f45913..f7a109149e 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -217,7 +217,7 @@ class MailboxRepositoryImpl extends MailboxRepository { } @override - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/domain/repository/mailbox_repository.dart b/lib/features/mailbox/domain/repository/mailbox_repository.dart index 4b9e319ed8..8f65b1ff93 100644 --- a/lib/features/mailbox/domain/repository/mailbox_repository.dart +++ b/lib/features/mailbox/domain/repository/mailbox_repository.dart @@ -32,7 +32,7 @@ abstract class MailboxRepository { Future renameMailbox(Session session, AccountId accountId, RenameMailboxRequest request); - Future> markAsMailboxRead( + Future> markAsMailboxRead( Session session, AccountId accountId, MailboxId mailboxId, diff --git a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart index 6f16d3e4e9..b79d7aed7a 100644 --- a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart +++ b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart @@ -4,7 +4,7 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; -class MarkAsMailboxReadLoading extends UIState {} +class MarkAsMailboxReadLoading extends LoadingState {} class UpdatingMarkAsMailboxReadState extends UIState { diff --git a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart index bd892595a3..17b99fc4bc 100644 --- a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart +++ b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart @@ -35,22 +35,22 @@ class MarkAsMailboxReadInteractor { final currentMailboxState = listState.first; final currentEmailState = listState.last; - final listEmails = await _mailboxRepository.markAsMailboxRead( + final listEmailId = await _mailboxRepository.markAsMailboxRead( session, accountId, mailboxId, totalEmailUnread, onProgressController); - if (totalEmailUnread == listEmails.length) { + if (totalEmailUnread == listEmailId.length) { yield Right(MarkAsMailboxReadAllSuccess( mailboxDisplayName, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); - } else if (listEmails.isNotEmpty) { + } else if (listEmailId.isNotEmpty) { yield Right(MarkAsMailboxReadHasSomeEmailFailure( mailboxDisplayName, - listEmails.length, + listEmailId.length, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else { diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index ebe5a7d76a..504c367f39 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -65,17 +65,13 @@ class TopBarThreadSelection extends StatelessWidget{ ), ), TMailButtonWidget.fromIcon( - icon: listEmail.isAllEmailRead - ? imagePaths.icUnread - : imagePaths.icRead, - tooltipMessage: listEmail.isAllEmailRead - ? AppLocalizations.of(context).mark_as_unread - : AppLocalizations.of(context).mark_as_read, + icon: _getIconForMarkAsRead(), + tooltipMessage: _getTooltipMessageForMarkAsRead(context), backgroundColor: Colors.transparent, iconSize: 24, onTapActionCallback: () => onEmailActionTypeAction?.call( List.from(listEmail), - listEmail.isAllEmailRead ? EmailActionType.markAsUnread : EmailActionType.markAsRead + _getActionTypeForMarkAsRead() ) ), TMailButtonWidget.fromIcon( @@ -163,4 +159,34 @@ class TopBarThreadSelection extends StatelessWidget{ ), ]); } + + EmailActionType _getActionTypeForMarkAsRead() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.markAllAsRead; + } else { + return listEmail.isAllEmailRead + ? EmailActionType.markAsUnread + : EmailActionType.markAsRead; + } + } + + String _getIconForMarkAsRead() { + if (isSelectAllEmailsEnabled) { + return imagePaths.icRead; + } else { + return listEmail.isAllEmailRead + ? imagePaths.icUnread + : imagePaths.icRead; + } + } + + String _getTooltipMessageForMarkAsRead(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).mark_all_as_read; + } else { + return listEmail.isAllEmailRead + ? AppLocalizations.of(context).mark_as_unread + : AppLocalizations.of(context).mark_as_read; + } + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index d5b6b5e075..31af5e228d 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -292,7 +292,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM mailboxDashBoardController.clearDashBoardAction(); } else if (action is HandleEmailActionTypeAction) { if (_validateToShowConfirmBulkActionEmailsDialog()) { - _showConfirmDialogWhenMakeToActionForSelectionAllEmails(); + _showConfirmDialogWhenMakeToActionForSelectionAllEmails(actionType: action.emailAction); } else { pressEmailSelectionAction( action.emailAction, @@ -1377,7 +1377,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM void showPopupMenuSelectionEmailAction(BuildContext context, RelativeRect position) { final listSelectionEmailActions = [ - EmailActionType.markAsRead, + EmailActionType.markAllAsRead, EmailActionType.markAsUnread, EmailActionType.moveToMailbox, EmailActionType.moveToTrash, @@ -1400,7 +1400,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM onCallbackAction: () { popBack(); if (!isSearchActive) { - _showConfirmDialogWhenMakeToActionForSelectionAllEmails(); + _showConfirmDialogWhenMakeToActionForSelectionAllEmails(actionType: action); } } ) @@ -1412,7 +1412,9 @@ class ThreadController extends BaseController with EmailActionController, PopupM return mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue; } - Future _showConfirmDialogWhenMakeToActionForSelectionAllEmails() async { + Future _showConfirmDialogWhenMakeToActionForSelectionAllEmails({ + required EmailActionType actionType, + }) async { final selectedMailbox = mailboxDashBoardController.selectedMailbox.value; if (currentContext == null || selectedMailbox == null) return; @@ -1430,6 +1432,39 @@ class ThreadController extends BaseController with EmailActionController, PopupM imagePaths.icQuotasWarning, colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), ), + onConfirmAction: () { + _handleActionsForSelectionAllEmails( + context: currentContext!, + selectedMailbox: selectedMailbox, + actionType: actionType + ); + } ); } + + void _handleActionsForSelectionAllEmails({ + required BuildContext context, + required PresentationMailbox selectedMailbox, + required EmailActionType actionType + }) { + if (_session == null || _accountId == null) { + logError('ThreadController::_handleActionsForSelectionAllEmails: SESSION & ACCOUNT_ID is null'); + return; + } + + switch(actionType) { + case EmailActionType.markAllAsRead: + cancelSelectEmail(); + mailboxDashBoardController.markAsReadMailbox( + _session!, + _accountId!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.unreadEmails?.value.value.toInt() ?? 0 + ); + break; + default: + break; + } + } } \ No newline at end of file diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index a125b4c3ae..a6bb9c787d 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -28,5 +28,6 @@ enum EmailActionType { composeFromUnsubscribeMailtoLink, archiveMessage, printAll, - downloadMessageAsEML + downloadMessageAsEML, + markAllAsRead, } \ No newline at end of file From 0daf6212fd682888343bdb71017ade14eeee1875 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 11:57:20 +0700 Subject: [PATCH 07/13] TF-2646 Handle mark all as unread for selection emails --- .../mixin/mailbox_action_handler_mixin.dart | 3 +- .../email_action_type_extension.dart | 3 + .../state/mark_as_mailbox_read_state.dart | 3 + .../presentation/mailbox_controller.dart | 5 + .../bindings/mailbox_dashboard_bindings.dart | 7 + .../mailbox_dashboard_controller.dart | 147 +++++++++++------- .../controller/spam_report_controller.dart | 2 +- .../mailbox_dashboard_view_web.dart | 4 + ...d_selection_all_emails_loading_widget.dart | 36 +++++ .../mark_mailbox_as_read_loading_widget.dart | 36 +++++ .../search_mailbox_controller.dart | 5 + .../data/datasource/thread_datasource.dart | 11 ++ .../local_thread_datasource_impl.dart | 14 ++ .../thread_datasource_impl.dart | 22 +++ .../data/network/thread_isolate_worker.dart | 80 ++++++++++ .../repository/thread_repository_impl.dart | 21 +++ .../domain/repository/thread_repository.dart | 11 ++ ..._as_unread_selection_all_emails_state.dart | 85 ++++++++++ ...nread_selection_all_emails_interactor.dart | 75 +++++++++ .../presentation/thread_controller.dart | 19 ++- lib/l10n/intl_messages.arb | 50 ++++++ lib/main/bindings/core/core_bindings.dart | 5 +- lib/main/localizations/app_localizations.dart | 42 +++++ lib/main/utils/toast_manager.dart | 76 ++++++++- model/lib/email/email_action_type.dart | 1 + .../presentation_mailbox_extension.dart | 2 + .../mailbox_dashboard_controller_test.dart | 5 + .../mailbox_dashboard_view_widget_test.dart | 5 + 28 files changed, 714 insertions(+), 61 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart create mode 100644 lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart create mode 100644 lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart diff --git a/lib/features/base/mixin/mailbox_action_handler_mixin.dart b/lib/features/base/mixin/mailbox_action_handler_mixin.dart index f124709534..37cc523220 100644 --- a/lib/features/base/mixin/mailbox_action_handler_mixin.dart +++ b/lib/features/base/mixin/mailbox_action_handler_mixin.dart @@ -34,14 +34,13 @@ mixin MailboxActionHandlerMixin { final session = dashboardController.sessionCurrent; final accountId = dashboardController.accountId.value; final mailboxId = presentationMailbox.id; - final countEmailsUnread = presentationMailbox.unreadEmails?.value.value ?? 0; if (session != null && accountId != null) { dashboardController.markAsReadMailbox( session, accountId, mailboxId, presentationMailbox.getDisplayName(context), - countEmailsUnread.toInt() + presentationMailbox.countUnreadEmails ); onCallbackAction?.call(context); diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 4a4ee610e9..717d798e18 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -129,6 +129,7 @@ extension EmailActionTypeExtension on EmailActionType { String getIcon(ImagePaths imagePaths) { switch(this) { case EmailActionType.markAsUnread: + case EmailActionType.markAllAsUnread: return imagePaths.icUnreadEmail; case EmailActionType.unSpam: return imagePaths.icNotSpam; @@ -178,6 +179,8 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).move_to_trash; case EmailActionType.markAllAsRead: return AppLocalizations.of(context).mark_all_as_read; + case EmailActionType.markAllAsUnread: + return AppLocalizations.of(context).markAllAsUnread; default: return ''; } diff --git a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart index b79d7aed7a..d77581b4cc 100644 --- a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart +++ b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart @@ -78,4 +78,7 @@ class MarkAsMailboxReadFailure extends FeatureFailure { required this.mailboxDisplayName, dynamic exception }) : super(exception: exception); + + @override + List get props => [mailboxDisplayName, ...super.props]; } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index ef6fc7cb7a..88344ee176 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -91,6 +91,7 @@ import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbo import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -304,6 +305,10 @@ class MailboxController extends BaseMailboxController _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } else if (success is GetRestoredDeletedMessageSuccess) { _refreshMailboxChanges(properties: MailboxConstants.propertiesDefault); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } }); }); diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 61f04d8fcc..4a28b59d09 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -118,6 +118,7 @@ import 'package:tmail_ui_user/features/thread/domain/repository/thread_repositor import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; @@ -184,6 +185,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); Get.put(AdvancedFilterController()); } @@ -359,6 +361,11 @@ class MailboxDashBoardBindings extends BaseBindings { Get.lazyPut(() => GetIdentityCacheOnWebInteractor( Get.find() )); + Get.lazyPut(() => MarkAllAsUnreadSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index d7cc94ce10..d4c8b17575 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -146,6 +146,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; @@ -153,6 +154,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_sta import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; @@ -209,6 +211,7 @@ class MailboxDashBoardController extends ReloadableController final GetRestoredDeletedMessageInterator _getRestoredDeletedMessageInteractor; final RemoveComposerCacheOnWebInteractor _removeComposerCacheOnWebInteractor; final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; + final MarkAllAsUnreadSelectionAllEmailsInteractor _markAllAsUnreadSelectionAllEmailsInteractor; GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -243,6 +246,7 @@ class MailboxDashBoardController extends ReloadableController final isRecoveringDeletedMessage = RxBool(false); final localFileDraggableAppState = Rxn(); final isSelectAllEmailsEnabled = RxBool(false); + final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; @@ -259,12 +263,16 @@ class MailboxDashBoardController extends ReloadableController DeepLinksManager? _deepLinksManager; StreamSubscription? _deepLinkDataStreamSubscription; - final StreamController> _progressStateController = - StreamController>.broadcast(); - Stream> get progressState => _progressStateController.stream; + late StreamSubscription _markAsReadMailboxStreamSubscription; + late StreamSubscription _refreshActionStreamSubscription; + late StreamSubscription _markAllAsUnreadSelectionAllEmailsStreamSubscription; + final StreamController> _markAsReadMailboxStreamController = + StreamController>.broadcast(); final StreamController _refreshActionEventController = StreamController.broadcast(); + final StreamController> _markAllAsUnreadSelectionAllEmailsStreamController = + StreamController>.broadcast(); final _notificationManager = LocalNotificationManager.instance; final _fcmService = FcmService.instance; @@ -295,6 +303,7 @@ class MailboxDashBoardController extends ReloadableController this._getRestoredDeletedMessageInteractor, this._removeComposerCacheOnWebInteractor, this._getAllIdentitiesInteractor, + this._markAllAsUnreadSelectionAllEmailsInteractor, ); @override @@ -367,7 +376,7 @@ class MailboxDashBoardController extends ReloadableController _deleteEmailPermanentlySuccess(success); } else if (success is MarkAsMailboxReadAllSuccess || success is MarkAsMailboxReadHasSomeEmailFailure) { - _markAsReadMailboxSuccess(success); + _handleMarkMailboxAsReadSuccess(success); } else if (success is GetAllVacationSuccess) { if (success.listVacationResponse.isNotEmpty) { vacationResponse.value = success.listVacationResponse.first; @@ -418,6 +427,9 @@ class MailboxDashBoardController extends ReloadableController goToComposer(ComposerArguments.fromSessionStorageBrowser(success.composerCache)); } else if (success is GetIdentityCacheOnWebSuccess) { goToSettings(); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess + || success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _handleMarkAllAsUnreadSelectionAllEmailsSuccess(success); } } @@ -432,10 +444,9 @@ class MailboxDashBoardController extends ReloadableController _handleUpdateEmailAsDraftsFailure(failure); } else if (failure is RemoveEmailDraftsFailure) { clearState(); - } else if (failure is MarkAsMailboxReadAllFailure) { - _markAsReadMailboxAllFailure(failure); - } else if (failure is MarkAsMailboxReadFailure) { - _markAsReadMailboxFailure(failure); + } else if (failure is MarkAsMailboxReadAllFailure + || failure is MarkAsMailboxReadFailure) { + _handleMarkMailboxAsReadFailure(failure); } else if (failure is GetEmailByIdFailure) { _handleGetEmailByIdFailure(failure); } else if (failure is RestoreDeletedMessageFailure) { @@ -447,6 +458,9 @@ class MailboxDashBoardController extends ReloadableController toastManager.showMessageFailure(failure); } else if (failure is GetComposerCacheFailure) { _handleIdentityCache(); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsFailure + || failure is MarkAllAsUnreadSelectionAllEmailsAllFailure) { + _handleMarkAllAsUnreadSelectionAllEmailsFailure(failure); } } @@ -628,14 +642,18 @@ class MailboxDashBoardController extends ReloadableController } void _registerStreamListener() { - progressState.listen((state) { + _markAsReadMailboxStreamSubscription = _markAsReadMailboxStreamController.stream.listen((state) { viewStateMarkAsReadMailbox.value = state; }); - _refreshActionEventController.stream + _refreshActionStreamSubscription = _refreshActionEventController.stream .debounceTime(const Duration(milliseconds: FcmUtils.durationMessageComing)) .listen(_handleRefreshActionWhenBackToApp); + _markAllAsUnreadSelectionAllEmailsStreamSubscription = _markAllAsUnreadSelectionAllEmailsStreamController.stream.listen((state) { + markAllAsUnreadSelectionAllEmailsViewState.value = state; + }); + _registerLocalNotificationStreamListener(); } @@ -1593,15 +1611,13 @@ class MailboxDashBoardController extends ReloadableController void markAsReadMailboxAction(BuildContext context) { final session = sessionCurrent; final currentAccountId = accountId.value; - final mailboxId = selectedMailbox.value?.id; - final countEmailsUnread = selectedMailbox.value?.unreadEmails?.value.value ?? 0; - if (session != null && currentAccountId != null && mailboxId != null) { + if (session != null && currentAccountId != null && selectedMailbox.value != null) { markAsReadMailbox( session, currentAccountId, - mailboxId, - selectedMailbox.value?.getDisplayName(context) ?? '', - countEmailsUnread.toInt() + selectedMailbox.value!.id, + selectedMailbox.value!.getDisplayName(context), + selectedMailbox.value!.countUnreadEmails ); } } @@ -1619,49 +1635,31 @@ class MailboxDashBoardController extends ReloadableController mailboxId, mailboxDisplayName, totalEmailsUnread, - _progressStateController)); + _markAsReadMailboxStreamController)); } - void _markAsReadMailboxSuccess(Success success) { + void _handleMarkMailboxAsReadSuccess(Success success) { viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (success is MarkAsMailboxReadAllSuccess) { - if (currentContext != null && currentOverlayContext != null) { - appToast.showToastSuccessMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsMailboxReadSuccess(success.mailboxDisplayName), - leadingSVGIcon: imagePaths.icReadToast); - } - } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { - if (currentContext != null && currentOverlayContext != null) { - appToast.showToastSuccessMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsMailboxReadHasSomeEmailFailure(success.mailboxDisplayName, success.countEmailsRead), - leadingSVGIcon: imagePaths.icReadToast); - } - } - } + if (currentContext == null || currentOverlayContext == null) return; - void _markAsReadMailboxFailure(MarkAsMailboxReadFailure failure) { - viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderAllFailure( - failure.mailboxDisplayName, - ) - ); - } + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); } - void _markAsReadMailboxAllFailure(MarkAsMailboxReadAllFailure failure) { + void _handleMarkMailboxAsReadFailure(Failure failure) { viewStateMarkAsReadMailbox.value = Right(UIState.idle); - if (currentOverlayContext != null && currentContext != null) { - appToast.showToastErrorMessage( - currentOverlayContext!, - AppLocalizations.of(currentContext!).toastMessageMarkAsReadFolderAllFailure(failure.mailboxDisplayName) - ); - } + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); } void goToComposer(ComposerArguments arguments) async { @@ -3012,6 +3010,47 @@ class MailboxDashBoardController extends ReloadableController accountId: accountId.value!); } + void markAllAsUnreadSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmailsRead + ) { + consumeState(_markAllAsUnreadSelectionAllEmailsInteractor.execute( + session, + accountId, + mailboxId, + mailboxDisplayName, + totalEmailsRead, + _markAllAsUnreadSelectionAllEmailsStreamController + )); + } + + void _handleMarkAllAsUnreadSelectionAllEmailsSuccess(Success success) { + markAllAsUnreadSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMarkAllAsUnreadSelectionAllEmailsFailure(Failure failure) { + markAllAsUnreadSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + @override void onClose() { if (PlatformInfo.isWeb) { @@ -3027,8 +3066,12 @@ class MailboxDashBoardController extends ReloadableController _emailReceiveManager.closeEmailReceiveManagerStream(); _deepLinkDataStreamSubscription?.cancel(); } - _progressStateController.close(); + _markAsReadMailboxStreamSubscription.cancel(); + _markAsReadMailboxStreamController.close(); + _refreshActionStreamSubscription.cancel(); _refreshActionEventController.close(); + _markAllAsUnreadSelectionAllEmailsStreamSubscription.cancel(); + _markAllAsUnreadSelectionAllEmailsStreamController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); applicationManager.releaseUserAgent(); diff --git a/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart index 418a882698..da2ac50faa 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart @@ -84,7 +84,7 @@ class SpamReportController extends BaseController { accountId, spamMailbox.id, spamMailbox.getDisplayName(context), - spamMailbox.unreadEmails?.value.value.toInt() ?? 0 + spamMailbox.countUnreadEmails ); presentationSpamMailbox.value = null; } diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index b09f43e8a6..4890b83ac8 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -25,6 +25,8 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/mai import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart'; @@ -133,6 +135,8 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } }), _buildListButtonQuickSearchFilter(context), + Obx(() => MarkMailboxAsReadLoadingWidget(viewState: controller.viewStateMarkAsReadMailbox.value)), + Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value)), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..910d9c1dd7 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart @@ -0,0 +1,36 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MarkAllAsUnreadSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAllAsUnreadSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalLoadingWidget + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsUpdating) { + final percent = success.countUnread / success.totalRead; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalPercentLoadingWidget(percent) + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart new file mode 100644 index 0000000000..1e34ee9212 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart @@ -0,0 +1,36 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; + +class MarkMailboxAsReadLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MarkMailboxAsReadLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAsMailboxReadLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalLoadingWidget + ); + } else if (success is UpdatingMarkAsMailboxReadState) { + final percent = success.countRead / success.totalUnread; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalPercentLoadingWidget(percent) + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index 9176583661..a72bd90a35 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -62,6 +62,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/dialog_router.dart'; @@ -167,6 +168,10 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _handleSubscribeMultipleMailboxHasSomeSuccess(success); } else if (success is CreateNewMailboxSuccess) { _createNewMailboxSuccess(success); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); } } diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index ccd7f46d89..4467573765 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -71,4 +74,12 @@ abstract class ThreadDataSource { ); Future getEmailById(Session session, AccountId accountId, EmailId emailId, {Properties? properties}); + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 769df93776..7f8e733f23 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -120,4 +123,15 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { Future getEmailById(Session session, AccountId accountId, EmailId emailId, {Properties? properties}) { throw UnimplementedError(); } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 48c317357a..41938fb743 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -133,4 +136,23 @@ class ThreadDataSourceImpl extends ThreadDataSource { return email.toPresentationEmail(); }).catchError(_exceptionThrower.throwException); } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + return Future.sync(() async { + return await _threadIsolateWorker.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 73b69e6666..54b39a6a1f 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -1,17 +1,24 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/sort/comparator.dart'; +import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; +import 'package:jmap_dart_client/jmap/core/utc_date.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_property.dart'; +import 'package:model/email/read_actions.dart'; import 'package:model/extensions/list_email_extension.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; import 'package:tmail_ui_user/features/caching/config/hive_cache_config.dart'; @@ -19,6 +26,8 @@ import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; import 'package:tmail_ui_user/features/thread/data/model/empty_mailbox_folder_arguments.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/domain/exceptions/thread_exceptions.dart'; +import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart'; import 'package:worker_manager/worker_manager.dart'; @@ -158,4 +167,75 @@ class ThreadIsolateWorker { log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}'); return emailListCompleted; } + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: mailboxId, + hasKeyword: KeyWordIdentifier.emailSeen.value, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) { + var listEmails = response.emailList; + if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) { + listEmails = listEmails + .where((email) => email.id != lastEmailId) + .toList(); + } + return EmailsResponse(emailList: listEmails, state: response.state); + }); + final listEmailRead = emailResponse.emailList; + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails: LIST_EMAIL_READ = ${listEmailRead?.length}'); + if (listEmailRead == null || listEmailRead.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmailRead.last.id; + lastReceivedDate = listEmailRead.last.receivedAt; + + final listEmailId = await _emailAPI.markAsRead( + session, + accountId, + listEmailRead, + ReadActions.markAsUnread + ); + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): MARK_UNREAD: ${listEmailId.length}'); + emailIdListCompleted.addAll(listEmailId); + onProgressController.add( + dartz.Right(MarkAllAsUnreadSelectionAllEmailsUpdating( + mailboxId: mailboxId, + totalRead: totalEmailRead, + countUnread: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): TOTAL_UNREAD: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } } diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 3541a07f63..8a79cfd999 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -1,5 +1,9 @@ +import 'dart:async'; + import 'package:core/data/model/source_type/data_source_type.dart'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; @@ -408,4 +412,21 @@ class ThreadRepositoryImpl extends ThreadRepository { return listEmailIdDeleted; } + + @override + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ) { + return mapDataSource[DataSourceType.network]!.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController + ); + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index 87e6ea37bb..ec078954f7 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -1,5 +1,8 @@ import 'dart:async'; +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; @@ -72,4 +75,12 @@ abstract class ThreadRepository { AccountId accountId, MailboxId spamMailboxId, ); + + Future> markAllAsUnreadForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmailRead, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart new file mode 100644 index 0000000000..7a41d31a70 --- /dev/null +++ b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart @@ -0,0 +1,85 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsLoading extends LoadingState {} + +class MarkAllAsUnreadSelectionAllEmailsUpdating extends UIState { + + final MailboxId mailboxId; + final int totalRead; + final int countUnread; + + MarkAllAsUnreadSelectionAllEmailsUpdating({ + required this.mailboxId, + required this.totalRead, + required this.countUnread + }); + + @override + List get props => [mailboxId, totalRead, countUnread]; +} + +class MarkAllAsUnreadSelectionAllEmailsAllSuccess extends UIActionState { + + final String mailboxDisplayName; + + MarkAllAsUnreadSelectionAllEmailsAllSuccess(this.mailboxDisplayName, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentMailboxState, currentEmailState); + + @override + List get props => [ + mailboxDisplayName, + ...super.props + ]; +} + +class MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + + final String mailboxDisplayName; + final int countEmailsUnread; + + MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( + this.mailboxDisplayName, + this.countEmailsUnread, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentMailboxState, currentEmailState); + + @override + List get props => [ + mailboxDisplayName, + countEmailsUnread, + ...super.props + ]; +} + +class MarkAllAsUnreadSelectionAllEmailsAllFailure extends FeatureFailure { + final String mailboxDisplayName; + + MarkAllAsUnreadSelectionAllEmailsAllFailure({required this.mailboxDisplayName}); + + @override + List get props => [mailboxDisplayName]; +} + +class MarkAllAsUnreadSelectionAllEmailsFailure extends FeatureFailure { + + final String mailboxDisplayName; + + MarkAllAsUnreadSelectionAllEmailsFailure({ + required this.mailboxDisplayName, + dynamic exception + }) : super(exception: exception); + + @override + List get props => [mailboxDisplayName, ...super.props]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..a166b4916f --- /dev/null +++ b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart @@ -0,0 +1,75 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; + +class MarkAllAsUnreadSelectionAllEmailsInteractor { + final MailboxRepository _mailboxRepository; + final EmailRepository _emailRepository; + final ThreadRepository _threadRepository; + + MarkAllAsUnreadSelectionAllEmailsInteractor( + this._mailboxRepository, + this._emailRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmailRead, + StreamController> onProgressController + ) async* { + try { + yield Right(MarkAllAsUnreadSelectionAllEmailsLoading()); + onProgressController.add(Right(MarkAllAsUnreadSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.markAllAsUnreadForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmailRead, + onProgressController); + + if (totalEmailRead == listEmailId.length) { + yield Right(MarkAllAsUnreadSelectionAllEmailsAllSuccess( + mailboxDisplayName, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( + mailboxDisplayName, + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MarkAllAsUnreadSelectionAllEmailsAllFailure( + mailboxDisplayName: mailboxDisplayName + )); + } + } catch (e) { + yield Left(MarkAllAsUnreadSelectionAllEmailsFailure( + mailboxDisplayName: mailboxDisplayName, + exception: e + )); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 31af5e228d..9b1f09df25 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -61,6 +61,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_st import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; @@ -402,6 +403,10 @@ class ThreadController extends BaseController with EmailActionController, PopupM _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is StoreEventAttendanceStatusSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _refreshEmailChanges(currentEmailState: success.currentEmailState); } }); }); @@ -1378,7 +1383,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM void showPopupMenuSelectionEmailAction(BuildContext context, RelativeRect position) { final listSelectionEmailActions = [ EmailActionType.markAllAsRead, - EmailActionType.markAsUnread, + EmailActionType.markAllAsUnread, EmailActionType.moveToMailbox, EmailActionType.moveToTrash, ]; @@ -1460,7 +1465,17 @@ class ThreadController extends BaseController with EmailActionController, PopupM _accountId!, selectedMailbox.mailboxId!, selectedMailbox.getDisplayName(context), - selectedMailbox.unreadEmails?.value.value.toInt() ?? 0 + selectedMailbox.countUnreadEmails + ); + break; + case EmailActionType.markAllAsUnread: + cancelSelectEmail(); + mailboxDashBoardController.markAllAsUnreadSelectionAllEmails( + _session!, + _accountId!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countReadEmails ); break; default: diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c3e9561bc9..ff2af45e15 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4263,5 +4263,55 @@ "total": {}, "folderName": {} } + }, + "markAllAsUnread": "Mark all as unread", + "@markAllAsUnread": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess": "You’ve marked all mails as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure": "You’ve marked {count} mails as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure": "All mails could not be marked as unread", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason": "All mails could not be marked as unread. Due \"{reason}\"", + "@toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } + }, + "toastMessageMarkAsReadFolderFailureWithReason": "Folder \"{folderName}\" could not be marked as read. Due \"{reason}\"", + "@toastMessageMarkAsReadFolderFailureWithReason": { + "type": "text", + "placeholders_order": [ + "folderName", + "reason" + ], + "placeholders": { + "folderName": {}, + "reason": {} + } } } \ No newline at end of file diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index b220c3e363..fa0fe97dbe 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -50,7 +50,10 @@ class CoreBindings extends Bindings { void _bindingToast() { Get.put(AppToast()); - Get.put(ToastManager(Get.find())); + Get.put(ToastManager( + Get.find(), + Get.find(), + )); } void _bindingDeviceManager() { diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 4685edcfc4..d31c7edbb2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4467,4 +4467,46 @@ class AppLocalizations { args: [total, folderName] ); } + + String get markAllAsUnread { + return Intl.message( + 'Mark all as unread', + name: 'markAllAsUnread', + ); + } + + String get toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess { + return Intl.message( + 'You’ve marked all mails as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess'); + } + + String toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure(int count) { + return Intl.message( + 'You’ve marked $count mails as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure', + args: [count]); + } + + String get toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure { + return Intl.message( + 'All mails could not be marked as unread', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure'); + } + + String toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be marked as unread. Due "$reason"', + name: 'toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason', + args: [reason] + ); + } + + String toastMessageMarkAsReadFolderFailureWithReason(String folderName, String reason) { + return Intl.message( + 'Folder "$folderName" could not be marked as read. Due "$reason"', + name: 'toastMessageMarkAsReadFolderFailureWithReason', + args: [folderName, reason] + ); + } } \ No newline at end of file diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index 595b20f6a4..fdf20e4130 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -1,4 +1,6 @@ +import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/utils/app_logger.dart'; import 'package:flutter/material.dart'; @@ -9,18 +11,21 @@ import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.d import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ToastManager { - final AppToast appToast; + final AppToast _appToast; + final ImagePaths _imagePaths; - ToastManager(this.appToast); + ToastManager(this._appToast, this._imagePaths); String? getMessageByException(BuildContext context, dynamic exception) { if (exception is CanNotFoundBaseUrl) { @@ -84,7 +89,72 @@ class ToastManager { } if (message?.isNotEmpty == true) { - appToast.showToastErrorMessage(currentOverlayContext!, message!); + _appToast.showToastErrorMessage(currentOverlayContext!, message!); + } + } + + void showSuccessMessage({ + required BuildContext context, + required BuildContext overlayContext, + required Success success + }) { + if (success is MarkAsMailboxReadAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsMailboxReadSuccess(success.mailboxDisplayName), + leadingSVGIcon: _imagePaths.icReadToast, + ); + } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsMailboxReadHasSomeEmailFailure(success.mailboxDisplayName, success.countEmailsRead), + leadingSVGIcon: _imagePaths.icReadToast, + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsSuccess, + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure(success.countEmailsUnread), + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } + } + + void showFailureMessage({ + required BuildContext context, + required BuildContext overlayContext, + required Failure failure + }) { + if (failure is MarkAsMailboxReadFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsReadFolderFailureWithReason( + failure.mailboxDisplayName, + failure.exception.toString(), + ), + ); + } else if (failure is MarkAsMailboxReadAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAsReadFolderAllFailure(failure.mailboxDisplayName), + ); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsFailureWithReason( + failure.exception.toString(), + ), + ); + } else if (failure is MarkAllAsUnreadSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure, + ); } } } diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index a6bb9c787d..785b51370f 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -30,4 +30,5 @@ enum EmailActionType { printAll, downloadMessageAsEML, markAllAsRead, + markAllAsUnread, } \ No newline at end of file diff --git a/model/lib/extensions/presentation_mailbox_extension.dart b/model/lib/extensions/presentation_mailbox_extension.dart index eb58c78aa7..1775364cbd 100644 --- a/model/lib/extensions/presentation_mailbox_extension.dart +++ b/model/lib/extensions/presentation_mailbox_extension.dart @@ -30,6 +30,8 @@ extension PresentationMailboxExtension on PresentationMailbox { int get countTotalEmails => totalEmails?.value.value.toInt() ?? 0; + int get countReadEmails => countTotalEmails > countUnreadEmails ? countTotalEmails - countUnreadEmails : 0; + bool get isInbox => role == PresentationMailbox.roleInbox; bool get isSpam => role == PresentationMailbox.roleSpam || role == PresentationMailbox.roleJunk; diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index b890e6126c..0379baa25c 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -84,6 +84,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; @@ -178,6 +179,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { // mock mailbox dashboard controller direct dependencies @@ -263,6 +265,7 @@ void main() { final refreshAllMailboxInteractor = MockRefreshAllMailboxInteractor(); final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); + final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); late MailboxController mailboxController; // mock thread controller direct dependencies @@ -314,6 +317,7 @@ void main() { Get.put(updateAccountCacheInteractor); Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); + Get.put(markAllAsUnreadSelectionAllEmailsInteractor); searchController = SearchController( quickSearchEmailInteractor, @@ -349,6 +353,7 @@ void main() { getRestoredDeletedMessageInteractor, removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, + markAllAsUnreadSelectionAllEmailsInteractor, ); }); diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index a090c81866..bb098eefe9 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -82,6 +82,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; @@ -180,6 +181,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { final moveToMailboxInteractor = MockMoveToMailboxInteractor(); @@ -250,6 +252,7 @@ void main() { final refreshAllMailboxInteractor = MockRefreshAllMailboxInteractor(); final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); + final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); final getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); final refreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); @@ -312,6 +315,7 @@ void main() { Get.put(updateAccountCacheInteractor); Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); + Get.put(markAllAsUnreadSelectionAllEmailsInteractor); when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([])); @@ -348,6 +352,7 @@ void main() { getRestoredDeletedMessageInteractor, removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, + markAllAsUnreadSelectionAllEmailsInteractor, ); Get.put(mailboxDashboardController); mailboxDashboardController.onReady(); From a3ebc54636bd91ad43bc9704ae2475873d5e4db4 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 14:29:37 +0700 Subject: [PATCH 08/13] TF-2646 Handle move all to folder for selection emails --- .../email_action_type_extension.dart | 3 + .../email/data/network/email_api.dart | 41 ++++++++++ .../state/mark_as_mailbox_read_state.dart | 9 ++- .../mark_as_mailbox_read_interactor.dart | 2 + .../presentation/mailbox_controller.dart | 5 ++ .../bindings/mailbox_dashboard_bindings.dart | 7 ++ .../mailbox_dashboard_controller.dart | 75 +++++++++++++++++ .../mailbox_dashboard_view_web.dart | 2 + ...l_selection_all_emails_loading_widget.dart | 36 +++++++++ .../widgets/top_bar_thread_selection.dart | 20 ++++- .../search_mailbox_controller.dart | 5 ++ .../data/datasource/thread_datasource.dart | 9 +++ .../local_thread_datasource_impl.dart | 12 +++ .../thread_datasource_impl.dart | 21 +++++ .../data/network/thread_isolate_worker.dart | 73 ++++++++++++++++- .../repository/thread_repository_impl.dart | 19 +++++ .../domain/repository/thread_repository.dart | 9 +++ ..._as_unread_selection_all_emails_state.dart | 41 ++-------- .../move_all_selection_all_emails_state.dart | 81 +++++++++++++++++++ ...nread_selection_all_emails_interactor.dart | 11 +-- ...e_all_selection_all_emails_interactor.dart | 72 +++++++++++++++++ .../presentation/thread_controller.dart | 35 ++++++-- lib/l10n/intl_messages.arb | 50 ++++++++++++ lib/main/localizations/app_localizations.dart | 35 ++++++++ lib/main/utils/toast_manager.dart | 30 +++++++ model/lib/email/email_action_type.dart | 1 + .../mailbox_dashboard_controller_test.dart | 5 ++ .../mailbox_dashboard_view_widget_test.dart | 5 ++ 28 files changed, 658 insertions(+), 56 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart create mode 100644 lib/features/thread/domain/state/move_all_selection_all_emails_state.dart create mode 100644 lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index 717d798e18..f32e91fae2 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -147,6 +147,7 @@ extension EmailActionTypeExtension on EmailActionType { case EmailActionType.markAllAsRead: return imagePaths.icRead; case EmailActionType.moveToMailbox: + case EmailActionType.moveAll: return imagePaths.icMove; case EmailActionType.moveToTrash: return imagePaths.icDeleteComposer; @@ -181,6 +182,8 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).mark_all_as_read; case EmailActionType.markAllAsUnread: return AppLocalizations.of(context).markAllAsUnread; + case EmailActionType.moveAll: + return AppLocalizations.of(context).moveAll; default: return ''; } diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 7f0c5cd65c..93e42cadf5 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -52,6 +52,7 @@ import 'package:model/extensions/email_id_extensions.dart'; import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/list_email_extension.dart'; import 'package:model/extensions/list_email_id_extension.dart'; +import 'package:model/extensions/mailbox_extension.dart'; import 'package:model/extensions/mailbox_id_extension.dart'; import 'package:model/extensions/session_extension.dart'; import 'package:model/oidc/token_oidc.dart'; @@ -830,4 +831,44 @@ class EmailAPI with HandleSetErrorMixin { throw NotFoundEmailException(); } } + + Future> moveSelectionAllEmailsToFolder( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + List listEmailId, + ) async { + final moveProperties = destinationMailbox.isSpam + ? listEmailId.generateMapUpdateObjectMoveToSpam(currentMailboxId, destinationMailbox.id!) + : listEmailId.generateMapUpdateObjectMoveToMailbox(currentMailboxId, destinationMailbox.id!); + + final setEmailMethod = SetEmailMethod(accountId) + ..addUpdates(moveProperties); + + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final response = await (requestBuilder..usings(capabilities)) + .build() + .execute(); + + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize + ); + + final listIdUpdated = setEmailResponse?.updated?.keys.toList(); + final mapErrors = handleSetResponse([setEmailResponse]); + + if (listIdUpdated != null && mapErrors.isEmpty) { + final listEmailIdUpdated = listIdUpdated.map((id) => EmailId(id)).toList(); + return listEmailIdUpdated; + } else { + throw SetMethodException(mapErrors); + } + } } \ No newline at end of file diff --git a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart index d77581b4cc..c5b259aec0 100644 --- a/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart +++ b/lib/features/mailbox/domain/state/mark_as_mailbox_read_state.dart @@ -23,9 +23,12 @@ class UpdatingMarkAsMailboxReadState extends UIState { class MarkAsMailboxReadAllSuccess extends UIActionState { + final MailboxId mailboxId; final String mailboxDisplayName; - MarkAsMailboxReadAllSuccess(this.mailboxDisplayName, + MarkAsMailboxReadAllSuccess( + this.mailboxId, + this.mailboxDisplayName, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -34,6 +37,7 @@ class MarkAsMailboxReadAllSuccess extends UIActionState { @override List get props => [ + mailboxId, mailboxDisplayName, ...super.props ]; @@ -41,10 +45,12 @@ class MarkAsMailboxReadAllSuccess extends UIActionState { class MarkAsMailboxReadHasSomeEmailFailure extends UIActionState { + final MailboxId mailboxId; final String mailboxDisplayName; final int countEmailsRead; MarkAsMailboxReadHasSomeEmailFailure( + this.mailboxId, this.mailboxDisplayName, this.countEmailsRead, { @@ -55,6 +61,7 @@ class MarkAsMailboxReadHasSomeEmailFailure extends UIActionState { @override List get props => [ + mailboxId, mailboxDisplayName, countEmailsRead, ...super.props diff --git a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart index 17b99fc4bc..1dd3d3d2c8 100644 --- a/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart +++ b/lib/features/mailbox/domain/usecases/mark_as_mailbox_read_interactor.dart @@ -44,11 +44,13 @@ class MarkAsMailboxReadInteractor { if (totalEmailUnread == listEmailId.length) { yield Right(MarkAsMailboxReadAllSuccess( + mailboxId, mailboxDisplayName, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else if (listEmailId.isNotEmpty) { yield Right(MarkAsMailboxReadHasSomeEmailFailure( + mailboxId, mailboxDisplayName, listEmailId.length, currentEmailState: currentEmailState, diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 88344ee176..ba43d09ff0 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -93,6 +93,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_sta import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; @@ -309,6 +310,10 @@ class MailboxController extends BaseMailboxController _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } }); }); diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 4a28b59d09..58232738ee 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -121,6 +121,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_in import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; @@ -186,6 +187,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); Get.put(AdvancedFilterController()); } @@ -366,6 +368,11 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => MoveAllSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index d4c8b17575..47e8e04131 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -149,6 +149,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; @@ -157,6 +158,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_in import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart'; @@ -212,6 +214,7 @@ class MailboxDashBoardController extends ReloadableController final RemoveComposerCacheOnWebInteractor _removeComposerCacheOnWebInteractor; final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; final MarkAllAsUnreadSelectionAllEmailsInteractor _markAllAsUnreadSelectionAllEmailsInteractor; + final MoveAllSelectionAllEmailsInteractor _moveAllSelectionAllEmailsInteractor; GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -247,6 +250,7 @@ class MailboxDashBoardController extends ReloadableController final localFileDraggableAppState = Rxn(); final isSelectAllEmailsEnabled = RxBool(false); final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); + final moveAllSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; @@ -266,6 +270,7 @@ class MailboxDashBoardController extends ReloadableController late StreamSubscription _markAsReadMailboxStreamSubscription; late StreamSubscription _refreshActionStreamSubscription; late StreamSubscription _markAllAsUnreadSelectionAllEmailsStreamSubscription; + late StreamSubscription _moveAllSelectionAllEmailsStreamSubscription; final StreamController> _markAsReadMailboxStreamController = StreamController>.broadcast(); @@ -273,6 +278,8 @@ class MailboxDashBoardController extends ReloadableController StreamController.broadcast(); final StreamController> _markAllAsUnreadSelectionAllEmailsStreamController = StreamController>.broadcast(); + final StreamController> _moveAllSelectionAllEmailsStreamController = + StreamController>.broadcast(); final _notificationManager = LocalNotificationManager.instance; final _fcmService = FcmService.instance; @@ -304,6 +311,7 @@ class MailboxDashBoardController extends ReloadableController this._removeComposerCacheOnWebInteractor, this._getAllIdentitiesInteractor, this._markAllAsUnreadSelectionAllEmailsInteractor, + this._moveAllSelectionAllEmailsInteractor, ); @override @@ -430,6 +438,9 @@ class MailboxDashBoardController extends ReloadableController } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess || success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { _handleMarkAllAsUnreadSelectionAllEmailsSuccess(success); + } else if (success is MoveAllSelectionAllEmailsAllSuccess + || success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _handleMoveAllSelectionAllEmailsSuccess(success); } } @@ -461,6 +472,9 @@ class MailboxDashBoardController extends ReloadableController } else if (failure is MarkAllAsUnreadSelectionAllEmailsFailure || failure is MarkAllAsUnreadSelectionAllEmailsAllFailure) { _handleMarkAllAsUnreadSelectionAllEmailsFailure(failure); + } else if (failure is MoveAllSelectionAllEmailsFailure + || failure is MoveAllSelectionAllEmailsAllFailure) { + _handleMoveAllSelectionAllEmailsFailure(failure); } } @@ -654,6 +668,10 @@ class MailboxDashBoardController extends ReloadableController markAllAsUnreadSelectionAllEmailsViewState.value = state; }); + _moveAllSelectionAllEmailsStreamSubscription = _moveAllSelectionAllEmailsStreamController.stream.listen((state) { + moveAllSelectionAllEmailsViewState.value = state; + }); + _registerLocalNotificationStreamListener(); } @@ -3051,6 +3069,61 @@ class MailboxDashBoardController extends ReloadableController ); } + Future moveAllSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + final arguments = DestinationPickerArguments( + accountId, + MailboxActions.moveEmail, + session, + mailboxIdSelected: currentMailbox.id); + + final destinationMailbox = PlatformInfo.isWeb + ? await DialogRouter.pushGeneralDialog( + routeName: AppRoutes.destinationPicker, + arguments: arguments) + : await push(AppRoutes.destinationPicker, arguments: arguments); + + if (destinationMailbox is PresentationMailbox) { + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + destinationMailbox.toMailbox(), + destinationMailbox.mailboxPath ?? (context.mounted ? destinationMailbox.getDisplayName(context) : ''), + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController, + )); + } + } + + void _handleMoveAllSelectionAllEmailsSuccess(Success success) { + moveAllSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMoveAllSelectionAllEmailsFailure(Failure failure) { + moveAllSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + @override void onClose() { if (PlatformInfo.isWeb) { @@ -3072,6 +3145,8 @@ class MailboxDashBoardController extends ReloadableController _refreshActionEventController.close(); _markAllAsUnreadSelectionAllEmailsStreamSubscription.cancel(); _markAllAsUnreadSelectionAllEmailsStreamController.close(); + _moveAllSelectionAllEmailsStreamSubscription.cancel(); + _moveAllSelectionAllEmailsStreamController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); applicationManager.releaseUserAgent(); diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 4890b83ac8..0d3c4e6bcc 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -27,6 +27,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/ma import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart'; @@ -137,6 +138,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { _buildListButtonQuickSearchFilter(context), Obx(() => MarkMailboxAsReadLoadingWidget(viewState: controller.viewStateMarkAsReadMailbox.value)), Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value)), + Obx(() => MoveAllSelectionAllEmailsLoadingWidget(viewState: controller.moveAllSelectionAllEmailsViewState.value)), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..35c0fb1be8 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart @@ -0,0 +1,36 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; + +class MoveAllSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MoveAllSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MoveAllSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalLoadingWidget + ); + } else if (success is MoveAllSelectionAllEmailsUpdating) { + final percent = success.countMoved / success.total; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalPercentLoadingWidget(percent) + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index 504c367f39..ef58c504fa 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -93,11 +93,11 @@ class TopBarThreadSelection extends StatelessWidget{ TMailButtonWidget.fromIcon( icon: imagePaths.icMove, iconSize: 22, - tooltipMessage: AppLocalizations.of(context).move, + tooltipMessage: _getTooltipMessageForMove(context), backgroundColor: Colors.transparent, onTapActionCallback: () => onEmailActionTypeAction?.call( List.from(listEmail), - EmailActionType.moveToMailbox + _getActionTypeForMove() ) ), TMailButtonWidget.fromIcon( @@ -189,4 +189,20 @@ class TopBarThreadSelection extends StatelessWidget{ : AppLocalizations.of(context).mark_as_read; } } + + String _getTooltipMessageForMove(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).moveAll; + } else { + return AppLocalizations.of(context).move; + } + } + + EmailActionType _getActionTypeForMove() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.moveAll; + } else { + return EmailActionType.moveToMailbox; + } + } } \ No newline at end of file diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index a72bd90a35..0e3bd14354 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -63,6 +63,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dash import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/dialog_router.dart'; @@ -172,6 +173,10 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _refreshMailboxChanges(mailboxState: success.currentMailboxState); } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); } } diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index 4467573765..b6198384bd 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -82,4 +82,13 @@ abstract class ThreadDataSource { int totalEmailRead, StreamController> onProgressController ); + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 7f8e733f23..9f4d05ae46 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -134,4 +134,16 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { ) { throw UnimplementedError(); } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 41938fb743..beccf274c6 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -155,4 +155,25 @@ class ThreadDataSourceImpl extends ThreadDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ) { + return Future.sync(() async { + return await _threadIsolateWorker.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailbox, + totalEmails, + onProgressController + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 54b39a6a1f..2e0ecad71f 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -28,6 +28,7 @@ import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/domain/exceptions/thread_exceptions.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart'; import 'package:worker_manager/worker_manager.dart'; @@ -225,7 +226,6 @@ class ThreadIsolateWorker { emailIdListCompleted.addAll(listEmailId); onProgressController.add( dartz.Right(MarkAllAsUnreadSelectionAllEmailsUpdating( - mailboxId: mailboxId, totalRead: totalEmailRead, countUnread: emailIdListCompleted.length )) @@ -238,4 +238,75 @@ class ThreadIsolateWorker { log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): TOTAL_UNREAD: ${emailIdListCompleted.length}'); return emailIdListCompleted; } + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: currentMailboxId, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) { + var listEmails = response.emailList; + if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) { + listEmails = listEmails + .where((email) => email.id != lastEmailId) + .toList(); + } + return EmailsResponse(emailList: listEmails, state: response.state); + }); + final listEmail = emailResponse.emailList; + log('ThreadIsolateWorker::moveAllSelectionAllEmails: LIST_EMAIL = ${listEmail?.length}'); + if (listEmail == null || listEmail.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmail.last.id; + lastReceivedDate = listEmail.last.receivedAt; + + final listEmailId = await _emailAPI.moveSelectionAllEmailsToFolder( + session, + accountId, + currentMailboxId, + destinationMailbox, + listEmail.listEmailIds, + ); + log('ThreadIsolateWorker::moveAllSelectionAllEmails(): MOVED: ${listEmailId.length}'); + emailIdListCompleted.addAll(listEmailId); + onProgressController.add( + dartz.Right(MoveAllSelectionAllEmailsUpdating( + total: totalEmails, + countMoved: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + log('ThreadIsolateWorker::moveAllSelectionAllEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::moveAllSelectionAllEmails(): TOTAL_MOVED: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } } diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 8a79cfd999..5638c5e41b 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -429,4 +429,23 @@ class ThreadRepositoryImpl extends ThreadRepository { onProgressController ); } + + @override + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ) { + return mapDataSource[DataSourceType.network]!.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailbox, + totalEmails, + onProgressController + ); + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index ec078954f7..ba93b6c772 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -83,4 +83,13 @@ abstract class ThreadRepository { int totalEmailRead, StreamController> onProgressController ); + + Future> moveAllSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart index 7a41d31a70..d910e092bc 100644 --- a/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart +++ b/lib/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart @@ -1,52 +1,37 @@ import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; -import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; class MarkAllAsUnreadSelectionAllEmailsLoading extends LoadingState {} class MarkAllAsUnreadSelectionAllEmailsUpdating extends UIState { - final MailboxId mailboxId; final int totalRead; final int countUnread; MarkAllAsUnreadSelectionAllEmailsUpdating({ - required this.mailboxId, required this.totalRead, required this.countUnread }); @override - List get props => [mailboxId, totalRead, countUnread]; + List get props => [totalRead, countUnread]; } class MarkAllAsUnreadSelectionAllEmailsAllSuccess extends UIActionState { - final String mailboxDisplayName; - - MarkAllAsUnreadSelectionAllEmailsAllSuccess(this.mailboxDisplayName, - { - jmap.State? currentEmailState, - jmap.State? currentMailboxState, - } - ) : super(currentMailboxState, currentEmailState); - - @override - List get props => [ - mailboxDisplayName, - ...super.props - ]; + MarkAllAsUnreadSelectionAllEmailsAllSuccess({ + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentMailboxState, currentEmailState); } class MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure extends UIActionState { - final String mailboxDisplayName; final int countEmailsUnread; MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( - this.mailboxDisplayName, this.countEmailsUnread, { jmap.State? currentEmailState, @@ -56,30 +41,16 @@ class MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure extends UIActionState @override List get props => [ - mailboxDisplayName, countEmailsUnread, ...super.props ]; } -class MarkAllAsUnreadSelectionAllEmailsAllFailure extends FeatureFailure { - final String mailboxDisplayName; - - MarkAllAsUnreadSelectionAllEmailsAllFailure({required this.mailboxDisplayName}); - - @override - List get props => [mailboxDisplayName]; -} +class MarkAllAsUnreadSelectionAllEmailsAllFailure extends FeatureFailure {} class MarkAllAsUnreadSelectionAllEmailsFailure extends FeatureFailure { - final String mailboxDisplayName; - MarkAllAsUnreadSelectionAllEmailsFailure({ - required this.mailboxDisplayName, dynamic exception }) : super(exception: exception); - - @override - List get props => [mailboxDisplayName, ...super.props]; } \ No newline at end of file diff --git a/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart b/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart new file mode 100644 index 0000000000..3911508642 --- /dev/null +++ b/lib/features/thread/domain/state/move_all_selection_all_emails_state.dart @@ -0,0 +1,81 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MoveAllSelectionAllEmailsLoading extends LoadingState {} + +class MoveAllSelectionAllEmailsUpdating extends UIState { + + final int total; + final int countMoved; + + MoveAllSelectionAllEmailsUpdating({ + required this.total, + required this.countMoved + }); + + @override + List get props => [total, countMoved]; +} + +class MoveAllSelectionAllEmailsAllSuccess extends UIActionState { + final String destinationPath; + + MoveAllSelectionAllEmailsAllSuccess( + this.destinationPath, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentEmailState, currentMailboxState); + + @override + List get props => [ + destinationPath, + ...super.props + ]; +} + +class MoveAllSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + final String destinationPath; + final int countEmailsMoved; + + MoveAllSelectionAllEmailsHasSomeEmailFailure( + this.destinationPath, + this.countEmailsMoved, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentEmailState, currentMailboxState); + + @override + List get props => [ + countEmailsMoved, + destinationPath, + ...super.props + ]; +} + +class MoveAllSelectionAllEmailsAllFailure extends FeatureFailure { + final String destinationPath; + + MoveAllSelectionAllEmailsAllFailure(this.destinationPath); + + @override + List get props => [destinationPath]; +} + +class MoveAllSelectionAllEmailsFailure extends FeatureFailure { + + final String destinationPath; + + MoveAllSelectionAllEmailsFailure({ + required this.destinationPath, + dynamic exception + }) : super(exception: exception); + + @override + List get props => [destinationPath, ...super.props]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart index a166b4916f..94068cf929 100644 --- a/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart +++ b/lib/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart @@ -51,25 +51,18 @@ class MarkAllAsUnreadSelectionAllEmailsInteractor { if (totalEmailRead == listEmailId.length) { yield Right(MarkAllAsUnreadSelectionAllEmailsAllSuccess( - mailboxDisplayName, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else if (listEmailId.isNotEmpty) { yield Right(MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure( - mailboxDisplayName, listEmailId.length, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else { - yield Left(MarkAllAsUnreadSelectionAllEmailsAllFailure( - mailboxDisplayName: mailboxDisplayName - )); + yield Left(MarkAllAsUnreadSelectionAllEmailsAllFailure()); } } catch (e) { - yield Left(MarkAllAsUnreadSelectionAllEmailsFailure( - mailboxDisplayName: mailboxDisplayName, - exception: e - )); + yield Left(MarkAllAsUnreadSelectionAllEmailsFailure(exception: e)); } } } \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..864ca594d9 --- /dev/null +++ b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart @@ -0,0 +1,72 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; + +class MoveAllSelectionAllEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + MoveAllSelectionAllEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId currentMailboxId, + Mailbox destinationMailbox, + String destinationPath, + int totalEmails, + StreamController> onProgressController + ) async* { + try { + yield Right(MoveAllSelectionAllEmailsLoading()); + onProgressController.add(Right(MoveAllSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.moveAllSelectionAllEmails( + session, + accountId, + currentMailboxId, + destinationMailbox, + totalEmails, + onProgressController); + + if (totalEmails == listEmailId.length) { + yield Right(MoveAllSelectionAllEmailsAllSuccess( + destinationPath, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MoveAllSelectionAllEmailsHasSomeEmailFailure( + destinationPath, + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MoveAllSelectionAllEmailsAllFailure(destinationPath)); + } + } catch (e) { + yield Left(MoveAllSelectionAllEmailsFailure(destinationPath: destinationPath, exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 9b1f09df25..890a3b5a5e 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -64,6 +64,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_stat import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_all_email_state.dart'; @@ -378,8 +379,14 @@ class ThreadController extends BaseController with EmailActionController, PopupM } else if (success is UpdateEmailDraftsSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAsMailboxReadAllSuccess) { + if (success.mailboxId == selectedMailbox?.id) { + cancelSelectEmail(); + } _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAsMailboxReadHasSomeEmailFailure) { + if (success.mailboxId == selectedMailbox?.id) { + cancelSelectEmail(); + } _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MoveMultipleEmailToMailboxAllSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); @@ -404,8 +411,16 @@ class ThreadController extends BaseController with EmailActionController, PopupM } else if (success is StoreEventAttendanceStatusSuccess) { _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAllAsUnreadSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); _refreshEmailChanges(currentEmailState: success.currentEmailState); } else if (success is MarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); _refreshEmailChanges(currentEmailState: success.currentEmailState); } }); @@ -1372,19 +1387,17 @@ class ThreadController extends BaseController with EmailActionController, PopupM bool validateToShowSelectionEmailsBanner() { return mailboxDashBoardController.isSelectionEnabled() && - mailboxDashBoardController.selectedMailbox.value != null && - mailboxDashBoardController.selectedMailbox.value!.totalEmails != null && + selectedMailbox != null && + selectedMailbox!.totalEmails != null && mailboxDashBoardController.listEmailSelected.length < - mailboxDashBoardController - .selectedMailbox.value!.totalEmails!.value.value - .toInt(); + selectedMailbox!.countTotalEmails; } void showPopupMenuSelectionEmailAction(BuildContext context, RelativeRect position) { final listSelectionEmailActions = [ EmailActionType.markAllAsRead, EmailActionType.markAllAsUnread, - EmailActionType.moveToMailbox, + EmailActionType.moveAll, EmailActionType.moveToTrash, ]; @@ -1459,7 +1472,6 @@ class ThreadController extends BaseController with EmailActionController, PopupM switch(actionType) { case EmailActionType.markAllAsRead: - cancelSelectEmail(); mailboxDashBoardController.markAsReadMailbox( _session!, _accountId!, @@ -1469,7 +1481,6 @@ class ThreadController extends BaseController with EmailActionController, PopupM ); break; case EmailActionType.markAllAsUnread: - cancelSelectEmail(); mailboxDashBoardController.markAllAsUnreadSelectionAllEmails( _session!, _accountId!, @@ -1478,6 +1489,14 @@ class ThreadController extends BaseController with EmailActionController, PopupM selectedMailbox.countReadEmails ); break; + case EmailActionType.moveAll: + mailboxDashBoardController.moveAllSelectionAllEmails( + context, + _session!, + _accountId!, + selectedMailbox, + ); + break; default: break; } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index ff2af45e15..68dffb7926 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4313,5 +4313,55 @@ "folderName": {}, "reason": {} } + }, + "toastMessageMoveAllSelectionAllEmailsSuccess": "You’ve moved all mails to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [ + "destinationPath" + ], + "placeholders": { + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure": "You’ve moved {count} mails to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count", + "destinationPath" + ], + "placeholders": { + "count": {}, + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsAllFailure": "All mails could not be moved to {destinationPath}", + "@toastMessageMoveAllSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [ + "destinationPath" + ], + "placeholders": { + "destinationPath": {} + } + }, + "toastMessageMoveAllSelectionAllEmailsFailureWithReason": "All mails could not be moved to {destinationPath}. Due \"{reason}\"", + "@toastMessageMoveAllSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "destinationPath", + "reason" + ], + "placeholders": { + "destinationPath": {}, + "reason": {} + } + }, + "moveAll": "Move all", + "@moveAll": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index d31c7edbb2..a27c6bc587 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4509,4 +4509,39 @@ class AppLocalizations { args: [folderName, reason] ); } + + String toastMessageMoveAllSelectionAllEmailsSuccess(String destinationPath) { + return Intl.message( + 'You’ve moved all mails to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsSuccess', + args: [destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure(int count, String destinationPath) { + return Intl.message( + 'You’ve moved $count mails to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure', + args: [count, destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsAllFailure(String destinationPath) { + return Intl.message( + 'All mails could not be moved to $destinationPath', + name: 'toastMessageMoveAllSelectionAllEmailsAllFailure', + args: [destinationPath]); + } + + String toastMessageMoveAllSelectionAllEmailsFailureWithReason(String destinationPath, String reason) { + return Intl.message( + 'All mails could not be moved to $destinationPath. Due "$reason"', + name: 'toastMessageMoveAllSelectionAllEmailsFailureWithReason', + args: [destinationPath, reason] + ); + } + + String get moveAll { + return Intl.message( + 'Move all', + name: 'moveAll'); + } } \ No newline at end of file diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index fdf20e4130..c95a3b37d9 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_ import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -122,6 +123,20 @@ class ToastManager { AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsHasSomeEmailFailure(success.countEmailsUnread), leadingSVGIcon: _imagePaths.icUnreadToast, ); + } else if (success is MoveAllSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsSuccess(success.destinationPath), + leadingSVGIconColor: Colors.white, + leadingSVGIcon: _imagePaths.icFolderMailbox, + ); + } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsHasSomeEmailFailure(success.countEmailsMoved, success.destinationPath), + leadingSVGIconColor: Colors.white, + leadingSVGIcon: _imagePaths.icFolderMailbox, + ); } } @@ -155,6 +170,21 @@ class ToastManager { overlayContext, AppLocalizations.of(context).toastMessageMarkAllAsUnreadSelectionAllEmailsAllFailure, ); + } else if (failure is MoveAllSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsFailureWithReason( + failure.destinationPath, + failure.exception.toString(), + ), + ); + } else if (failure is MoveAllSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMoveAllSelectionAllEmailsAllFailure( + failure.destinationPath, + ), + ); } } } diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index 785b51370f..05f0c6e9fe 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -31,4 +31,5 @@ enum EmailActionType { downloadMessageAsEML, markAllAsRead, markAllAsUnread, + moveAll, } \ No newline at end of file diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index 0379baa25c..664880c6a2 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -87,6 +87,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_i import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; @@ -180,6 +181,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { // mock mailbox dashboard controller direct dependencies @@ -266,6 +268,7 @@ void main() { final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); + final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); late MailboxController mailboxController; // mock thread controller direct dependencies @@ -318,6 +321,7 @@ void main() { Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); Get.put(markAllAsUnreadSelectionAllEmailsInteractor); + Get.put(moveAllSelectionAllEmailsInteractor); searchController = SearchController( quickSearchEmailInteractor, @@ -354,6 +358,7 @@ void main() { removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, markAllAsUnreadSelectionAllEmailsInteractor, + moveAllSelectionAllEmailsInteractor, ); }); diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index bb098eefe9..9a1a5d3951 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -85,6 +85,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_i import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; @@ -182,6 +183,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { final moveToMailboxInteractor = MockMoveToMailboxInteractor(); @@ -253,6 +255,7 @@ void main() { final removeComposerCacheOnWebInteractor = MockRemoveComposerCacheOnWebInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); + final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); final getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); final refreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); @@ -316,6 +319,7 @@ void main() { Get.put(getAllIdentitiesInteractor); Get.put(removeComposerCacheOnWebInteractor); Get.put(markAllAsUnreadSelectionAllEmailsInteractor); + Get.put(moveAllSelectionAllEmailsInteractor); when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([])); @@ -353,6 +357,7 @@ void main() { removeComposerCacheOnWebInteractor, getAllIdentitiesInteractor, markAllAsUnreadSelectionAllEmailsInteractor, + moveAllSelectionAllEmailsInteractor, ); Get.put(mailboxDashboardController); mailboxDashboardController.onReady(); From b8a3954c939e05b4ef95199a607870b4ed73fc98 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 16:06:26 +0700 Subject: [PATCH 09/13] TF-2646 Handle move all to trash for selection emails --- .../email_action_type_extension.dart | 16 ++++ .../email/data/network/email_api.dart | 12 +-- .../mailbox_dashboard_controller.dart | 26 +++++- .../mailbox_dashboard_view_web.dart | 1 + .../widgets/top_bar_thread_selection.dart | 80 ++++++++++++++----- .../data/datasource/thread_datasource.dart | 7 +- .../local_thread_datasource_impl.dart | 7 +- .../thread_datasource_impl.dart | 12 ++- .../data/network/thread_isolate_worker.dart | 10 ++- .../repository/thread_repository_impl.dart | 12 ++- .../domain/repository/thread_repository.dart | 7 +- ...e_all_selection_all_emails_interactor.dart | 12 ++- .../presentation/thread_controller.dart | 19 ++++- lib/l10n/intl_messages.arb | 12 +++ lib/main/localizations/app_localizations.dart | 13 +++ model/lib/email/email_action_type.dart | 2 + 16 files changed, 196 insertions(+), 52 deletions(-) diff --git a/lib/features/composer/presentation/extensions/email_action_type_extension.dart b/lib/features/composer/presentation/extensions/email_action_type_extension.dart index f32e91fae2..904fa9efda 100644 --- a/lib/features/composer/presentation/extensions/email_action_type_extension.dart +++ b/lib/features/composer/presentation/extensions/email_action_type_extension.dart @@ -1,4 +1,5 @@ +import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/extensions/html_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:flutter/cupertino.dart'; @@ -150,6 +151,8 @@ extension EmailActionTypeExtension on EmailActionType { case EmailActionType.moveAll: return imagePaths.icMove; case EmailActionType.moveToTrash: + case EmailActionType.moveAllToTrash: + case EmailActionType.deleteAllPermanently: return imagePaths.icDeleteComposer; default: return ''; @@ -184,8 +187,21 @@ extension EmailActionTypeExtension on EmailActionType { return AppLocalizations.of(context).markAllAsUnread; case EmailActionType.moveAll: return AppLocalizations.of(context).moveAll; + case EmailActionType.moveAllToTrash: + return AppLocalizations.of(context).moveAllToTrash; + case EmailActionType.deleteAllPermanently: + return AppLocalizations.of(context).deleteAllPermanently; default: return ''; } } + + Color getIconColor() { + switch(this) { + case EmailActionType.deleteAllPermanently: + return AppColor.colorDeletePermanentlyButton; + default: + return AppColor.primaryColor; + } + } } \ No newline at end of file diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 93e42cadf5..0502d85f3e 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -52,7 +52,6 @@ import 'package:model/extensions/email_id_extensions.dart'; import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/list_email_extension.dart'; import 'package:model/extensions/list_email_id_extension.dart'; -import 'package:model/extensions/mailbox_extension.dart'; import 'package:model/extensions/mailbox_id_extension.dart'; import 'package:model/extensions/session_extension.dart'; import 'package:model/oidc/token_oidc.dart'; @@ -836,12 +835,15 @@ class EmailAPI with HandleSetErrorMixin { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, List listEmailId, + { + bool isDestinationSpamMailbox = false + } ) async { - final moveProperties = destinationMailbox.isSpam - ? listEmailId.generateMapUpdateObjectMoveToSpam(currentMailboxId, destinationMailbox.id!) - : listEmailId.generateMapUpdateObjectMoveToMailbox(currentMailboxId, destinationMailbox.id!); + final moveProperties = isDestinationSpamMailbox + ? listEmailId.generateMapUpdateObjectMoveToSpam(currentMailboxId, destinationMailboxId) + : listEmailId.generateMapUpdateObjectMoveToMailbox(currentMailboxId, destinationMailboxId); final setEmailMethod = SetEmailMethod(accountId) ..addUpdates(moveProperties); diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 47e8e04131..1dbc99c0e6 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -3092,10 +3092,11 @@ class MailboxDashBoardController extends ReloadableController session, accountId, currentMailbox.id, - destinationMailbox.toMailbox(), + destinationMailbox.id, destinationMailbox.mailboxPath ?? (context.mounted ? destinationMailbox.getDisplayName(context) : ''), currentMailbox.countTotalEmails, _moveAllSelectionAllEmailsStreamController, + isDestinationSpamMailbox: destinationMailbox.isSpam )); } } @@ -3124,6 +3125,29 @@ class MailboxDashBoardController extends ReloadableController ); } + Future moveAllToTrashSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + final trashMailboxId = getMailboxIdByRole(PresentationMailbox.roleTrash); + + if (trashMailboxId == null) return; + + final trashMailboxPath = mapMailboxById[trashMailboxId]?.getDisplayName(context) ?? ''; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + trashMailboxId, + trashMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + @override void onClose() { if (PlatformInfo.isWeb) { diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 0d3c4e6bcc..1ddb47ff17 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -247,6 +247,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { listEmail: listEmailSelected, mapMailbox: controller.mapMailboxById, isSelectAllEmailsEnabled: controller.isSelectAllEmailsEnabled.value, + selectedMailbox: controller.selectedMailbox.value, onCancelSelection: () => controller.dispatchAction(CancelSelectionAllEmailAction()), onEmailActionTypeAction: (listEmails, actionType) => diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index ef58c504fa..48db476256 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -8,6 +8,7 @@ import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/list_presentation_email_extension.dart'; +import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -23,6 +24,7 @@ class TopBarThreadSelection extends StatelessWidget{ final OnEmailActionTypeAction? onEmailActionTypeAction; final VoidCallback? onCancelSelection; final bool isSelectAllEmailsEnabled; + final PresentationMailbox? selectedMailbox; final OnMoreSelectedEmailAction? onMoreSelectedEmailAction; TopBarThreadSelection({ @@ -30,6 +32,7 @@ class TopBarThreadSelection extends StatelessWidget{ required this.listEmail, required this.mapMailbox, required this.isSelectAllEmailsEnabled, + this.selectedMailbox, this.onEmailActionTypeAction, this.onCancelSelection, this.onMoreSelectedEmailAction, @@ -37,11 +40,6 @@ class TopBarThreadSelection extends StatelessWidget{ @override Widget build(BuildContext context) { - final canDeletePermanently = listEmail.isAllCanDeletePermanently(mapMailbox); - final canSpamAndMove = listEmail.isAllCanSpamAndMove(mapMailbox); - final isAllSpam = listEmail.isAllSpam(mapMailbox); - final isAllBelongToTheSameMailbox = listEmail.isAllBelongToTheSameMailbox(mapMailbox); - return Row(children: [ TMailButtonWidget.fromIcon( icon: imagePaths.icClose, @@ -127,24 +125,13 @@ class TopBarThreadSelection extends StatelessWidget{ icon: imagePaths.icDeleteComposer, backgroundColor: Colors.transparent, iconSize: 20, - iconColor: canDeletePermanently - ? AppColor.colorDeletePermanentlyButton - : AppColor.primaryColor, - tooltipMessage: canDeletePermanently - ? AppLocalizations.of(context).delete_permanently - : AppLocalizations.of(context).move_to_trash, + iconColor: _getIconColorForMoveToTrash(), + tooltipMessage: _getTooltipMessageForMoveToTrash(context), onTapActionCallback: () { - if (canDeletePermanently) { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.deletePermanently - ); - } else { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.moveToTrash - ); - } + onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMoveToTrash(), + ); } ), const Spacer(), @@ -160,6 +147,19 @@ class TopBarThreadSelection extends StatelessWidget{ ]); } + bool get canDeletePermanently => listEmail.isAllCanDeletePermanently(mapMailbox); + + bool get canDeleteAllPermanently => isSelectAllEmailsEnabled + && (selectedMailbox?.isTrash == true + || selectedMailbox?.isSpam == true + || selectedMailbox?.isDrafts == true); + + bool get canSpamAndMove => listEmail.isAllCanSpamAndMove(mapMailbox); + + bool get isAllSpam => listEmail.isAllSpam(mapMailbox); + + bool get isAllBelongToTheSameMailbox => listEmail.isAllBelongToTheSameMailbox(mapMailbox); + EmailActionType _getActionTypeForMarkAsRead() { if (isSelectAllEmailsEnabled) { return EmailActionType.markAllAsRead; @@ -205,4 +205,40 @@ class TopBarThreadSelection extends StatelessWidget{ return EmailActionType.moveToMailbox; } } + + Color _getIconColorForMoveToTrash() { + if (canDeleteAllPermanently) { + return AppColor.colorDeletePermanentlyButton; + } else if (isSelectAllEmailsEnabled) { + return AppColor.primaryColor; + } else if (canDeletePermanently) { + return AppColor.colorDeletePermanentlyButton; + } else { + return AppColor.primaryColor; + } + } + + String _getTooltipMessageForMoveToTrash(BuildContext context) { + if (canDeleteAllPermanently) { + return AppLocalizations.of(context).deleteAllPermanently; + } else if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).moveAllToTrash; + } else if (canDeletePermanently) { + return AppLocalizations.of(context).delete_permanently; + } else { + return AppLocalizations.of(context).move_to_trash; + } + } + + EmailActionType _getActionTypeForMoveToTrash() { + if (canDeleteAllPermanently) { + return EmailActionType.deleteAllPermanently; + } else if (isSelectAllEmailsEnabled) { + return EmailActionType.moveAllToTrash; + } else if (canDeletePermanently) { + return EmailActionType.deletePermanently; + } else { + return EmailActionType.moveToTrash; + } + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index b6198384bd..07ac4a3821 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -87,8 +87,11 @@ abstract class ThreadDataSource { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 9f4d05ae46..0c9cca7a8e 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -140,9 +140,12 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ) { throw UnimplementedError(); } diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index beccf274c6..948888a5cc 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -161,18 +161,22 @@ class ThreadDataSourceImpl extends ThreadDataSource { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ) { return Future.sync(() async { return await _threadIsolateWorker.moveAllSelectionAllEmails( session, accountId, currentMailboxId, - destinationMailbox, + destinationMailboxId, totalEmails, - onProgressController + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox ); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 2e0ecad71f..1a8919b818 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -243,9 +243,12 @@ class ThreadIsolateWorker { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ) async { List emailIdListCompleted = List.empty(growable: true); try { @@ -290,8 +293,9 @@ class ThreadIsolateWorker { session, accountId, currentMailboxId, - destinationMailbox, + destinationMailboxId, listEmail.listEmailIds, + isDestinationSpamMailbox: isDestinationSpamMailbox ); log('ThreadIsolateWorker::moveAllSelectionAllEmails(): MOVED: ${listEmailId.length}'); emailIdListCompleted.addAll(listEmailId); diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 5638c5e41b..065fbc6f7d 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -435,17 +435,21 @@ class ThreadRepositoryImpl extends ThreadRepository { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ) { return mapDataSource[DataSourceType.network]!.moveAllSelectionAllEmails( session, accountId, currentMailboxId, - destinationMailbox, + destinationMailboxId, totalEmails, - onProgressController + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox ); } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index ba93b6c772..c4d2977948 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -88,8 +88,11 @@ abstract class ThreadRepository { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ); } \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart index 864ca594d9..e7c81711fb 100644 --- a/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart +++ b/lib/features/thread/domain/usecases/move_all_selection_all_emails_interactor.dart @@ -26,10 +26,13 @@ class MoveAllSelectionAllEmailsInteractor { Session session, AccountId accountId, MailboxId currentMailboxId, - Mailbox destinationMailbox, + MailboxId destinationMailboxId, String destinationPath, int totalEmails, - StreamController> onProgressController + StreamController> onProgressController, + { + bool isDestinationSpamMailbox = false + } ) async* { try { yield Right(MoveAllSelectionAllEmailsLoading()); @@ -47,9 +50,10 @@ class MoveAllSelectionAllEmailsInteractor { session, accountId, currentMailboxId, - destinationMailbox, + destinationMailboxId, totalEmails, - onProgressController); + onProgressController, + isDestinationSpamMailbox: isDestinationSpamMailbox); if (totalEmails == listEmailId.length) { yield Right(MoveAllSelectionAllEmailsAllSuccess( diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 890a3b5a5e..fc210481b8 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1388,7 +1388,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM bool validateToShowSelectionEmailsBanner() { return mailboxDashBoardController.isSelectionEnabled() && selectedMailbox != null && - selectedMailbox!.totalEmails != null && + selectedMailbox!.countTotalEmails > ThreadConstants.maxCountEmails && mailboxDashBoardController.listEmailSelected.length < selectedMailbox!.countTotalEmails; } @@ -1398,7 +1398,12 @@ class ThreadController extends BaseController with EmailActionController, PopupM EmailActionType.markAllAsRead, EmailActionType.markAllAsUnread, EmailActionType.moveAll, - EmailActionType.moveToTrash, + if (selectedMailbox?.isTrash == true || + selectedMailbox?.isSpam == true || + selectedMailbox?.isDrafts == true) + EmailActionType.deleteAllPermanently + else + EmailActionType.moveAllToTrash ]; openPopupMenuAction( @@ -1409,7 +1414,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM child: popupItem( action.getIcon(imagePaths), action.getTitle(context), - colorIcon: AppColor.colorTextButton, + colorIcon: action.getIconColor(), styleName: const TextStyle( fontWeight: FontWeight.w500, fontSize: 14, @@ -1497,6 +1502,14 @@ class ThreadController extends BaseController with EmailActionController, PopupM selectedMailbox, ); break; + case EmailActionType.moveAllToTrash: + mailboxDashBoardController.moveAllToTrashSelectionAllEmails( + context, + _session!, + _accountId!, + selectedMailbox, + ); + break; default: break; } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 68dffb7926..72ddcb56c3 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4363,5 +4363,17 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "moveAllToTrash": "Move all to trash", + "@moveAllToTrash": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "deleteAllPermanently": "Delete all permanently", + "@deleteAllPermanently": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index a27c6bc587..79611f1347 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4544,4 +4544,17 @@ class AppLocalizations { 'Move all', name: 'moveAll'); } + + String get moveAllToTrash { + return Intl.message( + 'Move all to trash', + name: 'moveAllToTrash', + ); + } + + String get deleteAllPermanently { + return Intl.message( + 'Delete all permanently', + name: 'deleteAllPermanently'); + } } \ No newline at end of file diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index 05f0c6e9fe..d6ed60bb00 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -32,4 +32,6 @@ enum EmailActionType { markAllAsRead, markAllAsUnread, moveAll, + moveAllToTrash, + deleteAllPermanently, } \ No newline at end of file From ae1e2c280dc66cfe568ca82f2eb688b747450486 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 18:35:59 +0700 Subject: [PATCH 10/13] TF-2646 Handle delete all permanently for selection emails --- contact/pubspec.lock | 4 +- contact/pubspec.yaml | 2 +- email_recovery/pubspec.lock | 4 +- email_recovery/pubspec.yaml | 2 +- fcm/pubspec.lock | 4 +- fcm/pubspec.yaml | 2 +- forward/pubspec.lock | 4 +- forward/pubspec.yaml | 2 +- .../presentation/mailbox_controller.dart | 3 + .../bindings/mailbox_dashboard_bindings.dart | 7 +++ .../mailbox_dashboard_controller.dart | 57 +++++++++++++++++ .../mailbox_dashboard_view_web.dart | 2 + ...all_permanently_emails_loading_widget.dart | 38 ++++++++++++ ...d_selection_all_emails_loading_widget.dart | 4 +- .../mark_mailbox_as_read_loading_widget.dart | 4 +- ...l_selection_all_emails_loading_widget.dart | 4 +- .../search_mailbox_controller.dart | 3 + .../data/datasource/thread_datasource.dart | 8 +++ .../local_thread_datasource_impl.dart | 11 ++++ .../thread_datasource_impl.dart | 19 ++++++ .../thread/data/network/thread_api.dart | 57 +++++++++++++++++ .../data/network/thread_isolate_worker.dart | 48 ++++++++++++++- .../repository/thread_repository_impl.dart | 24 ++++++++ .../domain/repository/thread_repository.dart | 8 +++ .../delete_all_permanently_emails_state.dart | 40 ++++++++++++ ...ete_all_permanently_emails_interactor.dart | 61 +++++++++++++++++++ .../presentation/thread_controller.dart | 12 ++++ lib/l10n/intl_messages.arb | 10 +++ lib/main/localizations/app_localizations.dart | 8 +++ lib/main/utils/toast_manager.dart | 13 ++++ model/pubspec.lock | 4 +- model/pubspec.yaml | 2 +- pubspec.lock | 4 +- pubspec.yaml | 2 +- rule_filter/pubspec.lock | 4 +- rule_filter/pubspec.yaml | 2 +- server_settings/pubspec.lock | 4 +- server_settings/pubspec.yaml | 2 +- .../mailbox_dashboard_controller_test.dart | 5 ++ .../mailbox_dashboard_view_widget_test.dart | 5 ++ 40 files changed, 469 insertions(+), 30 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart create mode 100644 lib/features/thread/domain/state/delete_all_permanently_emails_state.dart create mode 100644 lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart diff --git a/contact/pubspec.lock b/contact/pubspec.lock index a3f209a444..b852623621 100644 --- a/contact/pubspec.lock +++ b/contact/pubspec.lock @@ -659,8 +659,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/contact/pubspec.yaml b/contact/pubspec.yaml index b85a3cd0ff..4938454e2c 100644 --- a/contact/pubspec.yaml +++ b/contact/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/email_recovery/pubspec.lock b/email_recovery/pubspec.lock index 9977a0fb8c..8ff58703fc 100644 --- a/email_recovery/pubspec.lock +++ b/email_recovery/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/email_recovery/pubspec.yaml b/email_recovery/pubspec.yaml index 9ffdcbdd12..bb2cfc4374 100644 --- a/email_recovery/pubspec.yaml +++ b/email_recovery/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/fcm/pubspec.lock b/fcm/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/fcm/pubspec.lock +++ b/fcm/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/fcm/pubspec.yaml b/fcm/pubspec.yaml index e418fae836..32db3ff8ac 100644 --- a/fcm/pubspec.yaml +++ b/fcm/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/forward/pubspec.lock b/forward/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/forward/pubspec.lock +++ b/forward/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/forward/pubspec.yaml b/forward/pubspec.yaml index 6af273b33e..1c7ef30c27 100644 --- a/forward/pubspec.yaml +++ b/forward/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index ba43d09ff0..f61880a50d 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -89,6 +89,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; @@ -314,6 +315,8 @@ class MailboxController extends BaseMailboxController _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } }); }); diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 58232738ee..31a5a8c05a 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -115,6 +115,7 @@ import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_isolate_worker.dart'; import 'package:tmail_ui_user/features/thread/data/repository/thread_repository_impl.dart'; import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; @@ -188,6 +189,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); Get.put(AdvancedFilterController()); } @@ -373,6 +375,11 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => DeleteAllPermanentlyEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 1dbc99c0e6..3a12221061 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -143,6 +143,7 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_send import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; @@ -152,6 +153,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; @@ -215,6 +217,7 @@ class MailboxDashBoardController extends ReloadableController final GetAllIdentitiesInteractor _getAllIdentitiesInteractor; final MarkAllAsUnreadSelectionAllEmailsInteractor _markAllAsUnreadSelectionAllEmailsInteractor; final MoveAllSelectionAllEmailsInteractor _moveAllSelectionAllEmailsInteractor; + final DeleteAllPermanentlyEmailsInteractor _deleteAllPermanentlyEmailsInteractor; GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -251,6 +254,7 @@ class MailboxDashBoardController extends ReloadableController final isSelectAllEmailsEnabled = RxBool(false); final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); final moveAllSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); + final deleteAllPermanentlyEmailsViewState = Rx>(Right(UIState.idle)); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; @@ -271,6 +275,7 @@ class MailboxDashBoardController extends ReloadableController late StreamSubscription _refreshActionStreamSubscription; late StreamSubscription _markAllAsUnreadSelectionAllEmailsStreamSubscription; late StreamSubscription _moveAllSelectionAllEmailsStreamSubscription; + late StreamSubscription _deleteAllPermanentlyEmailsStreamSubscription; final StreamController> _markAsReadMailboxStreamController = StreamController>.broadcast(); @@ -280,6 +285,8 @@ class MailboxDashBoardController extends ReloadableController StreamController>.broadcast(); final StreamController> _moveAllSelectionAllEmailsStreamController = StreamController>.broadcast(); + final StreamController> _deleteAllPermanentlyEmailsStreamController = + StreamController>.broadcast(); final _notificationManager = LocalNotificationManager.instance; final _fcmService = FcmService.instance; @@ -312,6 +319,7 @@ class MailboxDashBoardController extends ReloadableController this._getAllIdentitiesInteractor, this._markAllAsUnreadSelectionAllEmailsInteractor, this._moveAllSelectionAllEmailsInteractor, + this._deleteAllPermanentlyEmailsInteractor, ); @override @@ -441,6 +449,8 @@ class MailboxDashBoardController extends ReloadableController } else if (success is MoveAllSelectionAllEmailsAllSuccess || success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { _handleMoveAllSelectionAllEmailsSuccess(success); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _handleDeleteAllPermanentlyEmailsSuccess(success); } } @@ -475,6 +485,8 @@ class MailboxDashBoardController extends ReloadableController } else if (failure is MoveAllSelectionAllEmailsFailure || failure is MoveAllSelectionAllEmailsAllFailure) { _handleMoveAllSelectionAllEmailsFailure(failure); + } else if (failure is DeleteAllPermanentlyEmailsFailure) { + _handleDeleteAllPermanentlyEmailsFailure(failure); } } @@ -672,6 +684,10 @@ class MailboxDashBoardController extends ReloadableController moveAllSelectionAllEmailsViewState.value = state; }); + _deleteAllPermanentlyEmailsStreamSubscription = _deleteAllPermanentlyEmailsStreamController.stream.listen((state) { + deleteAllPermanentlyEmailsViewState.value = state; + }); + _registerLocalNotificationStreamListener(); } @@ -3148,6 +3164,45 @@ class MailboxDashBoardController extends ReloadableController )); } + Future deleteAllPermanentlyEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + consumeState(_deleteAllPermanentlyEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + currentMailbox.countTotalEmails, + _deleteAllPermanentlyEmailsStreamController + )); + } + + void _handleDeleteAllPermanentlyEmailsSuccess(Success success) { + deleteAllPermanentlyEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleDeleteAllPermanentlyEmailsFailure(Failure failure) { + deleteAllPermanentlyEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + @override void onClose() { if (PlatformInfo.isWeb) { @@ -3171,6 +3226,8 @@ class MailboxDashBoardController extends ReloadableController _markAllAsUnreadSelectionAllEmailsStreamController.close(); _moveAllSelectionAllEmailsStreamSubscription.cancel(); _moveAllSelectionAllEmailsStreamController.close(); + _deleteAllPermanentlyEmailsStreamSubscription.cancel(); + _deleteAllPermanentlyEmailsStreamController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); applicationManager.releaseUserAgent(); diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 1ddb47ff17..b0337a01e4 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -25,6 +25,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/mai import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/download/download_task_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart'; @@ -139,6 +140,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { Obx(() => MarkMailboxAsReadLoadingWidget(viewState: controller.viewStateMarkAsReadMailbox.value)), Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value)), Obx(() => MoveAllSelectionAllEmailsLoadingWidget(viewState: controller.moveAllSelectionAllEmailsViewState.value)), + Obx(() => DeleteAllPermanentlyEmailsLoadingWidget(viewState: controller.deleteAllPermanentlyEmailsViewState.value)), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart new file mode 100644 index 0000000000..d9c0a5ec27 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; + +class DeleteAllPermanentlyEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const DeleteAllPermanentlyEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is DeleteAllPermanentlyEmailsLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalLoadingWidget + ); + } else if (success is DeleteAllPermanentlyEmailsUpdating) { + final percent = success.total > 0 + ? success.countDeleted / success.total + : 0.0; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalPercentLoadingWidget(percent) + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart index 910d9c1dd7..2752e9fb82 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart @@ -23,7 +23,9 @@ class MarkAllAsUnreadSelectionAllEmailsLoadingWidget extends StatelessWidget wit child: horizontalLoadingWidget ); } else if (success is MarkAllAsUnreadSelectionAllEmailsUpdating) { - final percent = success.countUnread / success.totalRead; + final percent = success.totalRead > 0 + ? success.countUnread / success.totalRead + : 0.0; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: horizontalPercentLoadingWidget(percent) diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart index 1e34ee9212..5926b23db6 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart @@ -23,7 +23,9 @@ class MarkMailboxAsReadLoadingWidget extends StatelessWidget with AppLoaderMixin child: horizontalLoadingWidget ); } else if (success is UpdatingMarkAsMailboxReadState) { - final percent = success.countRead / success.totalUnread; + final percent = success.totalUnread > 0 + ? success.countRead / success.totalUnread + : 0.0; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: horizontalPercentLoadingWidget(percent) diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart index 35c0fb1be8..951b8d7ad4 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart @@ -23,7 +23,9 @@ class MoveAllSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoa child: horizontalLoadingWidget ); } else if (success is MoveAllSelectionAllEmailsUpdating) { - final percent = success.countMoved / success.total; + final percent = success.total > 0 + ? success.countMoved / success.total + : 0.0; return Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), child: horizontalPercentLoadingWidget(percent) diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index 0e3bd14354..e24b3e34c2 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -62,6 +62,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -177,6 +178,8 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _refreshMailboxChanges(mailboxState: success.currentMailboxState); } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); } } diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index 07ac4a3821..761aec08c4 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -94,4 +94,12 @@ abstract class ThreadDataSource { bool isDestinationSpamMailbox = false } ); + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 0c9cca7a8e..645236307d 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -149,4 +149,15 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { ) { throw UnimplementedError(); } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 948888a5cc..71aa4ef6cb 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -180,4 +180,23 @@ class ThreadDataSourceImpl extends ThreadDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) { + return Future.sync(() async { + return await _threadIsolateWorker.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_api.dart b/lib/features/thread/data/network/thread_api.dart index bdb0b57d47..05ac95ae48 100644 --- a/lib/features/thread/data/network/thread_api.dart +++ b/lib/features/thread/data/network/thread_api.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:core/utils/app_logger.dart'; +import 'package:dartz/dartz.dart' as dartz; import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/filter/filter.dart'; @@ -16,6 +17,9 @@ import 'package:jmap_dart_client/jmap/jmap_request.dart'; import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/changes/changes_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_comparator_property.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/get/get_email_response.dart'; import 'package:jmap_dart_client/jmap/mail/email/query/query_email_method.dart'; @@ -24,6 +28,9 @@ import 'package:tmail_ui_user/features/thread/data/extensions/list_email_extensi import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet.dart'; import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_method.dart'; import 'package:jmap_dart_client/jmap/mail/email/search_snippet/search_snippet_get_response.dart'; +import 'package:jmap_dart_client/jmap/mail/email/set/set_email_method.dart'; +import 'package:jmap_dart_client/jmap/mail/email/set/set_email_response.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/thread/data/model/email_change_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_emails_response.dart'; @@ -306,4 +313,54 @@ class ThreadAPI { return resultList!.list.first; } + + Future, List>> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId + ) async { + final processingInvocation = ProcessingInvocation(); + + final jmapRequestBuilder = JmapRequestBuilder(httpClient, processingInvocation); + + final queryEmailMethod = QueryEmailMethod(accountId) + ..addLimit(UnsignedInt(30)) + ..addFilters(EmailFilterCondition(inMailbox: mailboxId)) + ..addSorts({}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + )); + + final queryEmailInvocation = jmapRequestBuilder.invocation(queryEmailMethod); + + final setEmailMethod = SetEmailMethod(accountId) + ..addReferenceDestroy( + processingInvocation.createResultReference( + queryEmailInvocation.methodCallId, + ReferencePath.idsPath + ) + ); + + final setEmailInvocation = jmapRequestBuilder.invocation(setEmailMethod); + + final capabilities = setEmailMethod.requiredCapabilities + .toCapabilitiesSupportTeamMailboxes(session, accountId); + + final result = await (jmapRequestBuilder..usings(capabilities)) + .build() + .execute(); + + final queryEmailResponse = result.parse( + queryEmailInvocation.methodCallId, + QueryEmailResponse.deserialize); + + final setEmailResponse = result.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize); + + final listMatchedEmailId = queryEmailResponse?.ids.map((id) => EmailId(id)).toList() ?? []; + final listDeletedEmailId = setEmailResponse?.destroyed?.map((id) => EmailId(id)).toList() ?? []; + log('ThreadAPI::deleteAllPermanentlyEmails:listMatchedEmailId = $listMatchedEmailId'); + log('ThreadAPI::deleteAllPermanentlyEmails:listDeletedEmailId = $listDeletedEmailId'); + return dartz.Tuple2(listMatchedEmailId, listDeletedEmailId); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 1a8919b818..ffa096a296 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -163,7 +163,7 @@ class ThreadIsolateWorker { } } } catch (e) { - log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ERROR: $e'); + logError('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ERROR: $e'); } log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): TOTAL_REMOVE: ${emailListCompleted.length}'); return emailListCompleted; @@ -233,7 +233,7 @@ class ThreadIsolateWorker { } } } catch (e) { - log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): ERROR: $e'); + logError('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): ERROR: $e'); } log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails(): TOTAL_UNREAD: ${emailIdListCompleted.length}'); return emailIdListCompleted; @@ -308,9 +308,51 @@ class ThreadIsolateWorker { } } } catch (e) { - log('ThreadIsolateWorker::moveAllSelectionAllEmails(): ERROR: $e'); + logError('ThreadIsolateWorker::moveAllSelectionAllEmails(): ERROR: $e'); } log('ThreadIsolateWorker::moveAllSelectionAllEmails(): TOTAL_MOVED: ${emailIdListCompleted.length}'); return emailIdListCompleted; } + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + + while (mailboxHasEmails) { + final listResult = await _threadAPI.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId + ); + + if (listResult.value1.isNotEmpty) { + mailboxHasEmails = true; + } else { + mailboxHasEmails = false; + } + + if (listResult.value2.isNotEmpty) { + emailIdListCompleted.addAll(listResult.value2); + + onProgressController.add( + dartz.Right(MoveAllSelectionAllEmailsUpdating( + total: totalEmails, + countMoved: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::deleteAllPermanentlyEmails(): ERROR: $e'); + } + log('ThreadIsolateWorker::deleteAllPermanentlyEmails(): TOTAL_DELETED_PERMANENTLY: ${emailIdListCompleted.length}'); + return emailIdListCompleted; + } } diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 065fbc6f7d..0905f0a775 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -452,4 +452,28 @@ class ThreadRepositoryImpl extends ThreadRepository { isDestinationSpamMailbox: isDestinationSpamMailbox ); } + + @override + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ) async { + final listEmailIdDeleted = await mapDataSource[DataSourceType.network]!.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + + await _updateEmailCache( + accountId, + session.username, + newDestroyed: listEmailIdDeleted); + + return listEmailIdDeleted; + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index c4d2977948..8c22050e2d 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -95,4 +95,12 @@ abstract class ThreadRepository { bool isDestinationSpamMailbox = false } ); + + Future> deleteAllPermanentlyEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController, + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart b/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart new file mode 100644 index 0000000000..205664f38a --- /dev/null +++ b/lib/features/thread/domain/state/delete_all_permanently_emails_state.dart @@ -0,0 +1,40 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; + +class DeleteAllPermanentlyEmailsLoading extends LoadingState {} + +class DeleteAllPermanentlyEmailsUpdating extends UIState { + + final int total; + final int countDeleted; + + DeleteAllPermanentlyEmailsUpdating({ + required this.total, + required this.countDeleted + }); + + @override + List get props => [total, countDeleted]; +} + +class DeleteAllPermanentlyEmailsSuccess extends UIActionState { + + final List emailIds; + + DeleteAllPermanentlyEmailsSuccess( + this.emailIds, { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentEmailState, currentMailboxState); + + @override + List get props => [emailIds, ...super.props]; +} + +class DeleteAllPermanentlyEmailsFailure extends FeatureFailure { + + DeleteAllPermanentlyEmailsFailure(dynamic exception) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart b/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart new file mode 100644 index 0000000000..d813143753 --- /dev/null +++ b/lib/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart @@ -0,0 +1,61 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; + +class DeleteAllPermanentlyEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + DeleteAllPermanentlyEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) async* { + try { + yield Right(DeleteAllPermanentlyEmailsLoading()); + onProgressController.add(Right(DeleteAllPermanentlyEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final emailIdDeleted = await _threadRepository.deleteAllPermanentlyEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + + yield Right(DeleteAllPermanentlyEmailsSuccess( + emailIdDeleted, + currentMailboxState: currentMailboxState, + currentEmailState: currentEmailState, + )); + } catch (e) { + yield Left(DeleteAllPermanentlyEmailsFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index fc210481b8..64e7b614c2 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -56,6 +56,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; @@ -422,6 +423,9 @@ class ThreadController extends BaseController with EmailActionController, PopupM } else if (success is MoveAllSelectionAllEmailsHasSomeEmailFailure) { cancelSelectEmail(); _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); } }); }); @@ -1510,6 +1514,14 @@ class ThreadController extends BaseController with EmailActionController, PopupM selectedMailbox, ); break; + case EmailActionType.deleteAllPermanently: + mailboxDashBoardController.deleteAllPermanentlyEmails( + context, + _session!, + _accountId!, + selectedMailbox, + ); + break; default: break; } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 72ddcb56c3..cd23e6b6ef 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4375,5 +4375,15 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "toastMessageDeleteAllPermanentlyEmailsFailureWithReason": "All mails could not be delete forever. Due \"{reason}\"", + "@toastMessageDeleteAllPermanentlyEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 79611f1347..3ae621adb2 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4557,4 +4557,12 @@ class AppLocalizations { 'Delete all permanently', name: 'deleteAllPermanently'); } + + String toastMessageDeleteAllPermanentlyEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be delete forever. Due "$reason"', + name: 'toastMessageDeleteAllPermanentlyEmailsFailureWithReason', + args: [reason] + ); + } } \ No newline at end of file diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index c95a3b37d9..173f88ef9f 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_ex import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; @@ -137,6 +138,11 @@ class ToastManager { leadingSVGIconColor: Colors.white, leadingSVGIcon: _imagePaths.icFolderMailbox, ); + } else if (success is DeleteAllPermanentlyEmailsSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toast_message_empty_trash_folder_success, + ); } } @@ -185,6 +191,13 @@ class ToastManager { failure.destinationPath, ), ); + } else if (failure is DeleteAllPermanentlyEmailsFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageDeleteAllPermanentlyEmailsFailureWithReason( + failure.exception.toString(), + ), + ); } } } diff --git a/model/pubspec.lock b/model/pubspec.lock index 02e72ad737..96927594ad 100644 --- a/model/pubspec.lock +++ b/model/pubspec.lock @@ -651,8 +651,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/model/pubspec.yaml b/model/pubspec.yaml index 5c0a75a1e8..60d425fd4e 100644 --- a/model/pubspec.yaml +++ b/model/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### cupertino_icons: 1.0.6 diff --git a/pubspec.lock b/pubspec.lock index 3e18616358..1145207e8f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1315,8 +1315,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 78b5717ad3..ce5df981c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -65,7 +65,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property contacts_service: git: diff --git a/rule_filter/pubspec.lock b/rule_filter/pubspec.lock index 92848214ba..9002e98da8 100644 --- a/rule_filter/pubspec.lock +++ b/rule_filter/pubspec.lock @@ -295,8 +295,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/rule_filter/pubspec.yaml b/rule_filter/pubspec.yaml index bb8c5c7e0f..07f7cf33e2 100644 --- a/rule_filter/pubspec.yaml +++ b/rule_filter/pubspec.yaml @@ -15,7 +15,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/server_settings/pubspec.lock b/server_settings/pubspec.lock index fbf8947c30..ea7d962b2d 100644 --- a/server_settings/pubspec.lock +++ b/server_settings/pubspec.lock @@ -287,8 +287,8 @@ packages: dependency: "direct main" description: path: "." - ref: main - resolved-ref: bb4ffba9e3aba2cbb60c1173663abf975b521199 + ref: add-reference-destroy-property + resolved-ref: "1700754235ac124a55f90c38b81c2bfc661fc3b8" url: "https://github.com/linagora/jmap-dart-client.git" source: git version: "0.3.0" diff --git a/server_settings/pubspec.yaml b/server_settings/pubspec.yaml index 4e70d55723..c16a05d1dd 100644 --- a/server_settings/pubspec.yaml +++ b/server_settings/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: jmap_dart_client: git: url: https://github.com/linagora/jmap-dart-client.git - ref: main + ref: add-reference-destroy-property ### Dependencies from pub.dev ### equatable: 2.0.5 diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index 664880c6a2..614681b1ac 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -79,6 +79,7 @@ import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants. import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; @@ -182,6 +183,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { // mock mailbox dashboard controller direct dependencies @@ -269,6 +271,7 @@ void main() { final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); + final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); late MailboxController mailboxController; // mock thread controller direct dependencies @@ -322,6 +325,7 @@ void main() { Get.put(removeComposerCacheOnWebInteractor); Get.put(markAllAsUnreadSelectionAllEmailsInteractor); Get.put(moveAllSelectionAllEmailsInteractor); + Get.put(deleteAllPermanentlyEmailsInteractor); searchController = SearchController( quickSearchEmailInteractor, @@ -359,6 +363,7 @@ void main() { getAllIdentitiesInteractor, markAllAsUnreadSelectionAllEmailsInteractor, moveAllSelectionAllEmailsInteractor, + deleteAllPermanentlyEmailsInteractor, ); }); diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index 9a1a5d3951..161903891b 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -77,6 +77,7 @@ import 'package:tmail_ui_user/features/sending_queue/domain/usecases/update_send import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permanently_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; @@ -184,6 +185,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { final moveToMailboxInteractor = MockMoveToMailboxInteractor(); @@ -256,6 +258,7 @@ void main() { final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); + final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); final getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); final refreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); @@ -320,6 +323,7 @@ void main() { Get.put(removeComposerCacheOnWebInteractor); Get.put(markAllAsUnreadSelectionAllEmailsInteractor); Get.put(moveAllSelectionAllEmailsInteractor); + Get.put(deleteAllPermanentlyEmailsInteractor); when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([])); @@ -358,6 +362,7 @@ void main() { getAllIdentitiesInteractor, markAllAsUnreadSelectionAllEmailsInteractor, moveAllSelectionAllEmailsInteractor, + deleteAllPermanentlyEmailsInteractor, ); Get.put(mailboxDashboardController); mailboxDashboardController.onReady(); From b05496cb5e2044ae5f7d45dc27cabdffa1a52434 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 20:02:21 +0700 Subject: [PATCH 11/13] TF-2646 Handle mark all as starred for selection emails --- .../presentation/mailbox_controller.dart | 5 + .../bindings/mailbox_dashboard_bindings.dart | 7 + .../mailbox_dashboard_controller.dart | 61 +++++++++ .../mailbox_dashboard_view_web.dart | 2 + ...d_selection_all_emails_loading_widget.dart | 38 ++++++ .../widgets/top_bar_thread_selection.dart | 41 +++++- .../search_mailbox_controller.dart | 5 + .../data/datasource/thread_datasource.dart | 8 ++ .../local_thread_datasource_impl.dart | 11 ++ .../thread_datasource_impl.dart | 19 +++ .../data/network/thread_isolate_worker.dart | 122 ++++++++++++++---- .../repository/thread_repository_impl.dart | 17 +++ .../domain/repository/thread_repository.dart | 8 ++ ...as_starred_selection_all_emails_state.dart | 56 ++++++++ ...arred_selection_all_emails_interactor.dart | 68 ++++++++++ .../presentation/thread_controller.dart | 16 +++ lib/l10n/intl_messages.arb | 38 ++++++ lib/main/localizations/app_localizations.dart | 34 +++++ lib/main/utils/toast_manager.dart | 25 ++++ model/lib/email/email_action_type.dart | 1 + .../mailbox_dashboard_controller_test.dart | 5 + .../mailbox_dashboard_view_widget_test.dart | 5 + 22 files changed, 559 insertions(+), 33 deletions(-) create mode 100644 lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart create mode 100644 lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart create mode 100644 lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index f61880a50d..ac980dd968 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -92,6 +92,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; @@ -317,6 +318,10 @@ class MailboxController extends BaseMailboxController _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } else if (success is DeleteAllPermanentlyEmailsSuccess) { _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(currentMailboxState: success.currentMailboxState); } }); }); diff --git a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart index 31a5a8c05a..55b7314a7a 100644 --- a/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart +++ b/lib/features/mailbox_dashboard/presentation/bindings/mailbox_dashboard_bindings.dart @@ -119,6 +119,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permane import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; @@ -190,6 +191,7 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), )); Get.put(AdvancedFilterController()); } @@ -380,6 +382,11 @@ class MailboxDashBoardBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => MarkAllAsStarredSelectionAllEmailsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); } @override diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 3a12221061..7a0fa0c948 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -147,6 +147,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanentl import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; @@ -157,6 +158,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/delete_all_permane import 'package:tmail_ui_user/features/thread/domain/usecases/empty_spam_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; @@ -218,6 +220,7 @@ class MailboxDashBoardController extends ReloadableController final MarkAllAsUnreadSelectionAllEmailsInteractor _markAllAsUnreadSelectionAllEmailsInteractor; final MoveAllSelectionAllEmailsInteractor _moveAllSelectionAllEmailsInteractor; final DeleteAllPermanentlyEmailsInteractor _deleteAllPermanentlyEmailsInteractor; + final MarkAllAsStarredSelectionAllEmailsInteractor _markAllAsStarredSelectionAllEmailsInteractor; GetAllVacationInteractor? _getAllVacationInteractor; UpdateVacationInteractor? _updateVacationInteractor; @@ -255,6 +258,7 @@ class MailboxDashBoardController extends ReloadableController final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); final moveAllSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); final deleteAllPermanentlyEmailsViewState = Rx>(Right(UIState.idle)); + final markAllAsStarredSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); Session? sessionCurrent; Map mapDefaultMailboxIdByRole = {}; @@ -276,6 +280,7 @@ class MailboxDashBoardController extends ReloadableController late StreamSubscription _markAllAsUnreadSelectionAllEmailsStreamSubscription; late StreamSubscription _moveAllSelectionAllEmailsStreamSubscription; late StreamSubscription _deleteAllPermanentlyEmailsStreamSubscription; + late StreamSubscription _markAllAsStarredSelectionAllEmailsStreamSubscription; final StreamController> _markAsReadMailboxStreamController = StreamController>.broadcast(); @@ -287,6 +292,8 @@ class MailboxDashBoardController extends ReloadableController StreamController>.broadcast(); final StreamController> _deleteAllPermanentlyEmailsStreamController = StreamController>.broadcast(); + final StreamController> _markAllAsStarredSelectionAllEmailsStreamController = + StreamController>.broadcast(); final _notificationManager = LocalNotificationManager.instance; final _fcmService = FcmService.instance; @@ -320,6 +327,7 @@ class MailboxDashBoardController extends ReloadableController this._markAllAsUnreadSelectionAllEmailsInteractor, this._moveAllSelectionAllEmailsInteractor, this._deleteAllPermanentlyEmailsInteractor, + this._markAllAsStarredSelectionAllEmailsInteractor, ); @override @@ -451,6 +459,9 @@ class MailboxDashBoardController extends ReloadableController _handleMoveAllSelectionAllEmailsSuccess(success); } else if (success is DeleteAllPermanentlyEmailsSuccess) { _handleDeleteAllPermanentlyEmailsSuccess(success); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess + || success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _handleMarkAllAsStarredSelectionAllEmailsSuccess(success); } } @@ -487,6 +498,9 @@ class MailboxDashBoardController extends ReloadableController _handleMoveAllSelectionAllEmailsFailure(failure); } else if (failure is DeleteAllPermanentlyEmailsFailure) { _handleDeleteAllPermanentlyEmailsFailure(failure); + } else if (failure is MarkAllAsStarredSelectionAllEmailsFailure + || failure is MarkAllAsStarredSelectionAllEmailsAllFailure) { + _handleMarkAllAsStarredSelectionAllEmailsFailure(failure); } } @@ -688,6 +702,10 @@ class MailboxDashBoardController extends ReloadableController deleteAllPermanentlyEmailsViewState.value = state; }); + _markAllAsStarredSelectionAllEmailsStreamSubscription = _markAllAsStarredSelectionAllEmailsStreamController.stream.listen((state) { + markAllAsStarredSelectionAllEmailsViewState.value = state; + }); + _registerLocalNotificationStreamListener(); } @@ -3203,6 +3221,47 @@ class MailboxDashBoardController extends ReloadableController ); } + void markAllAsStarredSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmails + ) { + consumeState(_markAllAsStarredSelectionAllEmailsInteractor.execute( + session, + accountId, + mailboxId, + mailboxDisplayName, + totalEmails, + _markAllAsUnreadSelectionAllEmailsStreamController + )); + } + + void _handleMarkAllAsStarredSelectionAllEmailsSuccess(Success success) { + markAllAsStarredSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showSuccessMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + success: success, + ); + } + + void _handleMarkAllAsStarredSelectionAllEmailsFailure(Failure failure) { + markAllAsStarredSelectionAllEmailsViewState.value = Right(UIState.idle); + + if (currentContext == null || currentOverlayContext == null) return; + + toastManager.showFailureMessage( + context: currentContext!, + overlayContext: currentOverlayContext!, + failure: failure, + ); + } + @override void onClose() { if (PlatformInfo.isWeb) { @@ -3228,6 +3287,8 @@ class MailboxDashBoardController extends ReloadableController _moveAllSelectionAllEmailsStreamController.close(); _deleteAllPermanentlyEmailsStreamSubscription.cancel(); _deleteAllPermanentlyEmailsStreamController.close(); + _markAllAsStarredSelectionAllEmailsStreamSubscription.cancel(); + _markAllAsStarredSelectionAllEmailsStreamController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); applicationManager.releaseUserAgent(); diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index b0337a01e4..8a9a310653 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/do import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/navigation_bar/navigation_bar_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart'; @@ -141,6 +142,7 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value)), Obx(() => MoveAllSelectionAllEmailsLoadingWidget(viewState: controller.moveAllSelectionAllEmailsViewState.value)), Obx(() => DeleteAllPermanentlyEmailsLoadingWidget(viewState: controller.deleteAllPermanentlyEmailsViewState.value)), + Obx(() => MarkAllAsStarredSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsStarredSelectionAllEmailsViewState.value)), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart new file mode 100644 index 0000000000..c859468221 --- /dev/null +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart @@ -0,0 +1,38 @@ + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { + + final Either viewState; + + const MarkAllAsStarredSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + + @override + Widget build(BuildContext context) { + return viewState.fold( + (failure) => const SizedBox.shrink(), + (success) { + if (success is MarkAllAsStarredSelectionAllEmailsLoading) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalLoadingWidget + ); + } else if (success is MarkAllAsStarredSelectionAllEmailsUpdating) { + final percent = success.total > 0 + ? success.countStarred / success.total + : 0.0; + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + child: horizontalPercentLoadingWidget(percent) + ); + } + return const SizedBox.shrink(); + } + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index 48db476256..ee9f844613 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -73,17 +73,13 @@ class TopBarThreadSelection extends StatelessWidget{ ) ), TMailButtonWidget.fromIcon( - icon: listEmail.isAllEmailStarred - ? imagePaths.icUnStar - : imagePaths.icStar, - tooltipMessage: listEmail.isAllEmailStarred - ? AppLocalizations.of(context).un_star - : AppLocalizations.of(context).star, + icon: _getIconForMarkAsStar(), + tooltipMessage: _getTooltipMessageForMarkAsStar(context), backgroundColor: Colors.transparent, iconSize: 24, onTapActionCallback: () => onEmailActionTypeAction?.call( List.from(listEmail), - listEmail.isAllEmailStarred ? EmailActionType.unMarkAsStarred : EmailActionType.markAsStarred + _getActionTypeForMarkAsStar() ) ), if (canSpamAndMove) @@ -241,4 +237,35 @@ class TopBarThreadSelection extends StatelessWidget{ return EmailActionType.moveToTrash; } } + + String _getIconForMarkAsStar() { + if (isSelectAllEmailsEnabled) { + return imagePaths.icStar; + } else { + return listEmail.isAllEmailStarred + ? imagePaths.icUnStar + : imagePaths.icStar; + } + } + + String _getTooltipMessageForMarkAsStar(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return AppLocalizations.of(context).allStarred; + } else { + return listEmail.isAllEmailStarred + ? AppLocalizations.of(context).un_star + : AppLocalizations.of(context).star; + } + } + + + EmailActionType _getActionTypeForMarkAsStar() { + if (isSelectAllEmailsEnabled) { + return EmailActionType.markAllAsStarred; + } else { + return listEmail.isAllEmailStarred + ? EmailActionType.unMarkAsStarred + : EmailActionType.markAsStarred; + } + } } \ No newline at end of file diff --git a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart index e24b3e34c2..b6a6a30137 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_controller.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_controller.dart @@ -63,6 +63,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dash import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -180,6 +181,10 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa _refreshMailboxChanges(mailboxState: success.currentMailboxState); } else if (success is DeleteAllPermanentlyEmailsSuccess) { _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _refreshMailboxChanges(mailboxState: success.currentMailboxState); } } diff --git a/lib/features/thread/data/datasource/thread_datasource.dart b/lib/features/thread/data/datasource/thread_datasource.dart index 761aec08c4..c1c2788797 100644 --- a/lib/features/thread/data/datasource/thread_datasource.dart +++ b/lib/features/thread/data/datasource/thread_datasource.dart @@ -102,4 +102,12 @@ abstract class ThreadDataSource { int totalEmails, StreamController> onProgressController, ); + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart index 645236307d..d680f09361 100644 --- a/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/local_thread_datasource_impl.dart @@ -160,4 +160,15 @@ class LocalThreadDataSourceImpl extends ThreadDataSource { ) { throw UnimplementedError(); } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart index 71aa4ef6cb..fc98e1d530 100644 --- a/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart +++ b/lib/features/thread/data/datasource_impl/thread_datasource_impl.dart @@ -199,4 +199,23 @@ class ThreadDataSourceImpl extends ThreadDataSource { ); }).catchError(_exceptionThrower.throwException); } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + return Future.sync(() async { + return await _threadIsolateWorker.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController, + ); + }).catchError(_exceptionThrower.throwException); + } } \ No newline at end of file diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index ffa096a296..467afcaff2 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -18,6 +18,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_property.dart'; +import 'package:model/email/mark_star_action.dart'; import 'package:model/email/read_actions.dart'; import 'package:model/extensions/list_email_extension.dart'; import 'package:tmail_ui_user/features/base/isolate/background_isolate_binary_messenger/background_isolate_binary_messenger.dart'; @@ -27,6 +28,7 @@ import 'package:tmail_ui_user/features/thread/data/model/empty_mailbox_folder_ar import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart'; import 'package:tmail_ui_user/features/thread/domain/exceptions/thread_exceptions.dart'; import 'package:tmail_ui_user/features/thread/domain/model/email_response.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/main/exceptions/isolate_exception.dart'; @@ -141,15 +143,13 @@ class ThreadIsolateWorker { EmailComparator(EmailComparatorProperty.receivedAt) ..setIsAscending(false)), filter: EmailFilterCondition(inMailbox: mailboxId, before: lastEmail?.receivedAt), - properties: Properties({EmailProperty.id})); - - var newEmailList = emailsResponse.emailList ?? []; - if (lastEmail != null) { - newEmailList = newEmailList.where((email) => email.id != lastEmail!.id).toList(); - } - + properties: Properties({EmailProperty.id}) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmail?.id + )); + final newEmailList = emailsResponse.emailList ?? []; log('ThreadIsolateWorker::_emptyMailboxFolderOnWeb(): ${newEmailList.length}'); - if (newEmailList.isNotEmpty) { lastEmail = newEmailList.last; hasEmails = true; @@ -199,15 +199,10 @@ class ThreadIsolateWorker { EmailProperty.id, EmailProperty.receivedAt, }) - ).then((response) { - var listEmails = response.emailList; - if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) { - listEmails = listEmails - .where((email) => email.id != lastEmailId) - .toList(); - } - return EmailsResponse(emailList: listEmails, state: response.state); - }); + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); final listEmailRead = emailResponse.emailList; log('ThreadIsolateWorker::markAllAsUnreadForSelectionAllEmails: LIST_EMAIL_READ = ${listEmailRead?.length}'); if (listEmailRead == null || listEmailRead.isEmpty) { @@ -272,15 +267,10 @@ class ThreadIsolateWorker { EmailProperty.id, EmailProperty.receivedAt, }) - ).then((response) { - var listEmails = response.emailList; - if (listEmails != null && listEmails.isNotEmpty && lastEmailId != null) { - listEmails = listEmails - .where((email) => email.id != lastEmailId) - .toList(); - } - return EmailsResponse(emailList: listEmails, state: response.state); - }); + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); final listEmail = emailResponse.emailList; log('ThreadIsolateWorker::moveAllSelectionAllEmails: LIST_EMAIL = ${listEmail?.length}'); if (listEmail == null || listEmail.isEmpty) { @@ -355,4 +345,84 @@ class ThreadIsolateWorker { log('ThreadIsolateWorker::deleteAllPermanentlyEmails(): TOTAL_DELETED_PERMANENTLY: ${emailIdListCompleted.length}'); return emailIdListCompleted; } + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) async { + List emailIdListCompleted = List.empty(growable: true); + try { + bool mailboxHasEmails = true; + UTCDate? lastReceivedDate; + EmailId? lastEmailId; + + while (mailboxHasEmails) { + final emailResponse = await _threadAPI.getAllEmail( + session, + accountId, + limit: UnsignedInt(30), + filter: EmailFilterCondition( + inMailbox: mailboxId, + notKeyword: KeyWordIdentifier.emailFlagged.value, + before: lastReceivedDate + ), + sort: {}..add( + EmailComparator(EmailComparatorProperty.receivedAt)..setIsAscending(false) + ), + properties: Properties({ + EmailProperty.id, + EmailProperty.receivedAt, + }) + ).then((response) => _removeDuplicatedLatestEmailFromEmailResponse( + emailsResponse: response, + latestEmailId: lastEmailId + )); + final listEmails = emailResponse.emailList; + + if (listEmails == null || listEmails.isEmpty) { + mailboxHasEmails = false; + } else { + lastEmailId = listEmails.last.id; + lastReceivedDate = listEmails.last.receivedAt; + + final listResult = await _emailAPI.markAsStar( + session, + accountId, + listEmails, + MarkStarAction.markStar + ); + + emailIdListCompleted.addAll(listResult.listEmailIds); + onProgressController.add( + dartz.Right(MarkAllAsStarredSelectionAllEmailsUpdating( + total: totalEmails, + countStarred: emailIdListCompleted.length + )) + ); + } + } + } catch (e) { + logError('ThreadIsolateWorker::markAllAsStarredForSelectionAllEmails(): ERROR: $e'); + } + return emailIdListCompleted; + } + + EmailsResponse _removeDuplicatedLatestEmailFromEmailResponse({ + required EmailsResponse emailsResponse, + EmailId? latestEmailId, + }) { + List listEmails = emailsResponse.emailList ?? []; + if (listEmails.isNotEmpty && latestEmailId != null) { + listEmails = listEmails + .where((email) => email.id != latestEmailId) + .toList(); + + return EmailsResponse(emailList: listEmails, state: emailsResponse.state); + } else { + return emailsResponse; + } + } } diff --git a/lib/features/thread/data/repository/thread_repository_impl.dart b/lib/features/thread/data/repository/thread_repository_impl.dart index 0905f0a775..6d7209172c 100644 --- a/lib/features/thread/data/repository/thread_repository_impl.dart +++ b/lib/features/thread/data/repository/thread_repository_impl.dart @@ -476,4 +476,21 @@ class ThreadRepositoryImpl extends ThreadRepository { return listEmailIdDeleted; } + + @override + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ) { + return mapDataSource[DataSourceType.network]!.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController + ); + } } \ No newline at end of file diff --git a/lib/features/thread/domain/repository/thread_repository.dart b/lib/features/thread/domain/repository/thread_repository.dart index 8c22050e2d..bf833c8c89 100644 --- a/lib/features/thread/domain/repository/thread_repository.dart +++ b/lib/features/thread/domain/repository/thread_repository.dart @@ -103,4 +103,12 @@ abstract class ThreadRepository { int totalEmails, StreamController> onProgressController, ); + + Future> markAllAsStarredForSelectionAllEmails( + Session session, + AccountId accountId, + MailboxId mailboxId, + int totalEmails, + StreamController> onProgressController + ); } \ No newline at end of file diff --git a/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart b/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart new file mode 100644 index 0000000000..f489219b5d --- /dev/null +++ b/lib/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart @@ -0,0 +1,56 @@ +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsLoading extends LoadingState {} + +class MarkAllAsStarredSelectionAllEmailsUpdating extends UIState { + + final int total; + final int countStarred; + + MarkAllAsStarredSelectionAllEmailsUpdating({ + required this.total, + required this.countStarred + }); + + @override + List get props => [total, countStarred]; +} + +class MarkAllAsStarredSelectionAllEmailsAllSuccess extends UIActionState { + + MarkAllAsStarredSelectionAllEmailsAllSuccess({ + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + }) : super(currentMailboxState, currentEmailState); +} + +class MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure extends UIActionState { + + final int countStarred; + + MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure( + this.countStarred, + { + jmap.State? currentEmailState, + jmap.State? currentMailboxState, + } + ) : super(currentMailboxState, currentEmailState); + + @override + List get props => [ + countStarred, + ...super.props + ]; +} + +class MarkAllAsStarredSelectionAllEmailsAllFailure extends FeatureFailure {} + +class MarkAllAsStarredSelectionAllEmailsFailure extends FeatureFailure { + + MarkAllAsStarredSelectionAllEmailsFailure({ + dynamic exception + }) : super(exception: exception); +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart b/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart new file mode 100644 index 0000000000..bbdb1707ad --- /dev/null +++ b/lib/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/repository/thread_repository.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; + +class MarkAllAsStarredSelectionAllEmailsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ThreadRepository _threadRepository; + + MarkAllAsStarredSelectionAllEmailsInteractor( + this._emailRepository, + this._mailboxRepository, + this._threadRepository, + ); + + Stream> execute( + Session session, + AccountId accountId, + MailboxId mailboxId, + String mailboxDisplayName, + int totalEmails, + StreamController> onProgressController + ) async* { + try { + yield Right(MarkAllAsStarredSelectionAllEmailsLoading()); + onProgressController.add(Right(MarkAllAsStarredSelectionAllEmailsLoading())); + + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ], eagerError: true); + + final currentMailboxState = listState.first; + final currentEmailState = listState.last; + + final listEmailId = await _threadRepository.markAllAsStarredForSelectionAllEmails( + session, + accountId, + mailboxId, + totalEmails, + onProgressController); + + if (totalEmails == listEmailId.length) { + yield Right(MarkAllAsStarredSelectionAllEmailsAllSuccess( + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else if (listEmailId.isNotEmpty) { + yield Right(MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure( + listEmailId.length, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState)); + } else { + yield Left(MarkAllAsStarredSelectionAllEmailsAllFailure()); + } + } catch (e) { + yield Left(MarkAllAsStarredSelectionAllEmailsFailure(exception: e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 64e7b614c2..ef55e11e5b 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -62,6 +62,7 @@ import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_st import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_email_by_id_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; @@ -426,6 +427,12 @@ class ThreadController extends BaseController with EmailActionController, PopupM } else if (success is DeleteAllPermanentlyEmailsSuccess) { cancelSelectEmail(); _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + cancelSelectEmail(); + _refreshEmailChanges(currentEmailState: success.currentEmailState); } }); }); @@ -1522,6 +1529,15 @@ class ThreadController extends BaseController with EmailActionController, PopupM selectedMailbox, ); break; + case EmailActionType.markAllAsStarred: + mailboxDashBoardController.markAllAsStarredSelectionAllEmails( + _session!, + _accountId!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countTotalEmails + ); + break; default: break; } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index cd23e6b6ef..1b7e2ea7fc 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -4385,5 +4385,43 @@ "placeholders": { "reason": {} } + }, + "allStarred": "All starred", + "@allStarred": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsSuccess": "You’ve marked all mails as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsSuccess": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure": "You’ve marked {count} mails as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure": "All mails could not be marked as starred", + "@toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason": "All mails could not be marked as starred. Due \"{reason}\"", + "@toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason": { + "type": "text", + "placeholders_order": [ + "reason" + ], + "placeholders": { + "reason": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 3ae621adb2..6a9bea9eb9 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4565,4 +4565,38 @@ class AppLocalizations { args: [reason] ); } + + String get allStarred { + return Intl.message( + 'All starred', + name: 'allStarred', + ); + } + + String get toastMessageMarkAllAsStarredSelectionAllEmailsSuccess { + return Intl.message( + 'You’ve marked all mails as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsSuccess'); + } + + String toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure(int count) { + return Intl.message( + 'You’ve marked $count mails as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure', + args: [count]); + } + + String get toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure { + return Intl.message( + 'All mails could not be marked as starred', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure'); + } + + String toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason(String reason) { + return Intl.message( + 'All mails could not be marked as starred. Due "$reason"', + name: 'toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason', + args: [reason] + ); + } } \ No newline at end of file diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index 173f88ef9f..d6186e9d57 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -16,6 +16,7 @@ import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_ import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/delete_all_permanently_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_unread_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_all_selection_all_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; @@ -143,6 +144,18 @@ class ToastManager { overlayContext, AppLocalizations.of(context).toast_message_empty_trash_folder_success, ); + } else if (success is MarkAllAsStarredSelectionAllEmailsAllSuccess) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsSuccess, + leadingSVGIcon: _imagePaths.icUnreadToast, + ); + } else if (success is MarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure) { + _appToast.showToastSuccessMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsHasSomeEmailFailure(success.countStarred), + leadingSVGIcon: _imagePaths.icUnreadToast, + ); } } @@ -198,6 +211,18 @@ class ToastManager { failure.exception.toString(), ), ); + } else if (failure is MarkAllAsStarredSelectionAllEmailsFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsFailureWithReason( + failure.exception.toString(), + ), + ); + } else if (failure is MarkAllAsStarredSelectionAllEmailsAllFailure) { + _appToast.showToastErrorMessage( + overlayContext, + AppLocalizations.of(context).toastMessageMarkAllAsStarredSelectionAllEmailsAllFailure, + ); } } } diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index d6ed60bb00..aaea2bb6f6 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -34,4 +34,5 @@ enum EmailActionType { moveAll, moveAllToTrash, deleteAllPermanently, + markAllAsStarred, } \ No newline at end of file diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index 614681b1ac..914cd2f164 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -85,6 +85,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; @@ -184,6 +185,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { // mock mailbox dashboard controller direct dependencies @@ -272,6 +274,7 @@ void main() { final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); + final markAllAsStarredSelectionAllEmailsInteractor = MockMarkAllAsStarredSelectionAllEmailsInteractor(); late MailboxController mailboxController; // mock thread controller direct dependencies @@ -326,6 +329,7 @@ void main() { Get.put(markAllAsUnreadSelectionAllEmailsInteractor); Get.put(moveAllSelectionAllEmailsInteractor); Get.put(deleteAllPermanentlyEmailsInteractor); + Get.put(markAllAsStarredSelectionAllEmailsInteractor); searchController = SearchController( quickSearchEmailInteractor, @@ -364,6 +368,7 @@ void main() { markAllAsUnreadSelectionAllEmailsInteractor, moveAllSelectionAllEmailsInteractor, deleteAllPermanentlyEmailsInteractor, + markAllAsStarredSelectionAllEmailsInteractor, ); }); diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index 161903891b..e24b0962c2 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -83,6 +83,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/empty_trash_folder import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_in_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_starred_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_all_as_unread_selection_all_emails_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; @@ -186,6 +187,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), ]) void main() { final moveToMailboxInteractor = MockMoveToMailboxInteractor(); @@ -259,6 +261,7 @@ void main() { final markAllAsUnreadSelectionAllEmailsInteractor = MockMarkAllAsUnreadSelectionAllEmailsInteractor(); final moveAllSelectionAllEmailsInteractor = MockMoveAllSelectionAllEmailsInteractor(); final deleteAllPermanentlyEmailsInteractor = MockDeleteAllPermanentlyEmailsInteractor(); + final markAllAsStarredSelectionAllEmailsInteractor = MockMarkAllAsStarredSelectionAllEmailsInteractor(); final getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor(); final refreshChangesEmailsInMailboxInteractor = MockRefreshChangesEmailsInMailboxInteractor(); @@ -324,6 +327,7 @@ void main() { Get.put(markAllAsUnreadSelectionAllEmailsInteractor); Get.put(moveAllSelectionAllEmailsInteractor); Get.put(deleteAllPermanentlyEmailsInteractor); + Get.put(markAllAsStarredSelectionAllEmailsInteractor); when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([])); @@ -363,6 +367,7 @@ void main() { markAllAsUnreadSelectionAllEmailsInteractor, moveAllSelectionAllEmailsInteractor, deleteAllPermanentlyEmailsInteractor, + markAllAsStarredSelectionAllEmailsInteractor, ); Get.put(mailboxDashboardController); mailboxDashboardController.onReady(); From 0922b530b08fe030efe216c61c0198f95b8f005b Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 29 Apr 2024 20:21:43 +0700 Subject: [PATCH 12/13] TF-2646 Handle mark all as Spam/UnSpam for selection emails --- .../mailbox_dashboard_controller.dart | 46 ++++++++++++++++ .../widgets/top_bar_thread_selection.dart | 53 +++++++++++++------ .../presentation/thread_controller.dart | 16 ++++++ lib/main/localizations/app_localizations.dart | 14 +++++ model/lib/email/email_action_type.dart | 2 + 5 files changed, 116 insertions(+), 15 deletions(-) diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 7a0fa0c948..047fe16c95 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -3262,6 +3262,52 @@ class MailboxDashBoardController extends ReloadableController ); } + Future maskAllAsSpamSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + final spamMailboxId = getMailboxIdByRole(PresentationMailbox.roleSpam); + + if (spamMailboxId == null) return; + + final spamMailboxPath = mapMailboxById[spamMailboxId]?.getDisplayName(context) ?? ''; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + spamMailboxId, + spamMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + + Future allUnSpamSelectionAllEmails( + BuildContext context, + Session session, + AccountId accountId, + PresentationMailbox currentMailbox + ) async { + final inboxMailboxId = getMailboxIdByRole(PresentationMailbox.roleInbox); + + if (inboxMailboxId == null) return; + + final inboxMailboxPath = mapMailboxById[inboxMailboxId]?.getDisplayName(context) ?? ''; + + consumeState(_moveAllSelectionAllEmailsInteractor.execute( + session, + accountId, + currentMailbox.id, + inboxMailboxId, + inboxMailboxPath, + currentMailbox.countTotalEmails, + _moveAllSelectionAllEmailsStreamController + )); + } + @override void onClose() { if (PlatformInfo.isWeb) { diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart index ee9f844613..739c15f869 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart @@ -95,24 +95,15 @@ class TopBarThreadSelection extends StatelessWidget{ ) ), TMailButtonWidget.fromIcon( - icon: isAllSpam ? imagePaths.icNotSpam : imagePaths.icSpam, + icon: _getIconForMoveToSpam(), backgroundColor: Colors.transparent, iconSize: 24, - tooltipMessage: isAllSpam - ? AppLocalizations.of(context).un_spam - : AppLocalizations.of(context).mark_as_spam, + tooltipMessage: _getTooltipMessageForMoveToSpam(context), onTapActionCallback: () { - if (isAllSpam) { - onEmailActionTypeAction?.call( - List.from(listEmail), - EmailActionType.unSpam - ); - } else { - onEmailActionTypeAction?.call( - List.from(listEmail.listEmailCanSpam(mapMailbox)), - EmailActionType.moveToSpam - ); - } + onEmailActionTypeAction?.call( + List.from(listEmail.listEmailCanSpam(mapMailbox)), + _getActionTypeForMoveToSpam() + ); } ) ], @@ -268,4 +259,36 @@ class TopBarThreadSelection extends StatelessWidget{ : EmailActionType.markAsStarred; } } + + String _getIconForMoveToSpam() { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true ? imagePaths.icNotSpam : imagePaths.icSpam; + } else { + return isAllSpam ? imagePaths.icNotSpam : imagePaths.icSpam; + } + } + + String _getTooltipMessageForMoveToSpam(BuildContext context) { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true + ? AppLocalizations.of(context).allUnSpam + : AppLocalizations.of(context).markAllAsSpam; + } else { + return isAllSpam + ? AppLocalizations.of(context).un_spam + : AppLocalizations.of(context).mark_as_spam; + } + } + + EmailActionType _getActionTypeForMoveToSpam() { + if (isSelectAllEmailsEnabled) { + return selectedMailbox?.isSpam == true + ? EmailActionType.allUnSpam + : EmailActionType.markAllAsSpam; + } else { + return isAllSpam + ? EmailActionType.unSpam + : EmailActionType.moveToSpam; + } + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index ef55e11e5b..bc83a5a4f3 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1538,6 +1538,22 @@ class ThreadController extends BaseController with EmailActionController, PopupM selectedMailbox.countTotalEmails ); break; + case EmailActionType.markAllAsSpam: + mailboxDashBoardController.maskAllAsSpamSelectionAllEmails( + context, + _session!, + _accountId!, + selectedMailbox, + ); + break; + case EmailActionType.allUnSpam: + mailboxDashBoardController.allUnSpamSelectionAllEmails( + context, + _session!, + _accountId!, + selectedMailbox, + ); + break; default: break; } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 6a9bea9eb9..3a10860b92 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -4599,4 +4599,18 @@ class AppLocalizations { args: [reason] ); } + + String get allUnSpam { + return Intl.message( + 'All UnSpam', + name: 'allUnSpam', + ); + } + + String get markAllAsSpam { + return Intl.message( + 'Mark all as spam', + name: 'markAllAsSpam', + ); + } } \ No newline at end of file diff --git a/model/lib/email/email_action_type.dart b/model/lib/email/email_action_type.dart index aaea2bb6f6..f9063818a4 100644 --- a/model/lib/email/email_action_type.dart +++ b/model/lib/email/email_action_type.dart @@ -35,4 +35,6 @@ enum EmailActionType { moveAllToTrash, deleteAllPermanently, markAllAsStarred, + markAllAsSpam, + allUnSpam, } \ No newline at end of file From f33b954d4cf855546857766cbcdad70d8df360d5 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 16 Dec 2024 13:15:55 +0700 Subject: [PATCH 13/13] TF-2646 Handle move all selection emails when drag and drop to mailbox --- .../presentation/resources/assets_paths.dart | 1 - .../mixin/email_action_handler_mixin.dart | 37 ++++ .../presentation/mailbox_controller.dart | 6 + .../presentation/mailbox_view_web.dart | 23 ++- .../utils/mailbox_method_action_define.dart | 4 +- .../widgets/mailbox_item_widget.dart | 9 +- .../mailbox_dashboard_controller.dart | 193 +++++++++++++++--- .../mailbox_dashboard_view_web.dart | 56 +++-- .../top_bar_thread_selection.dart | 185 ++++++++--------- ...all_permanently_emails_loading_widget.dart | 8 +- ...d_selection_all_emails_loading_widget.dart | 16 +- ...d_selection_all_emails_loading_widget.dart | 8 +- .../mark_mailbox_as_read_loading_widget.dart | 8 +- ...l_selection_all_emails_loading_widget.dart | 8 +- .../presentation/search_mailbox_view.dart | 21 +- .../mailbox_searched_item_builder.dart | 11 +- .../data/network/thread_isolate_worker.dart | 1 + .../model/draggable_email_data.dart | 22 ++ .../presentation/thread_controller.dart | 163 ++++----------- .../thread/presentation/thread_view.dart | 81 ++++---- .../draggable/draggable_feedback_widget.dart | 52 +++++ ...ge_select_all_email_in_mailbox_widget.dart | 19 +- .../message_select_email_in_page_widget.dart | 19 +- .../select_all_emails_in_mailbox_banner.dart | 2 +- lib/l10n/intl_messages.arb | 34 ++- lib/main/localizations/app_localizations.dart | 22 +- pubspec.lock | 2 +- pubspec.yaml | 2 + .../controller/thread_controller_test.dart | 2 + 29 files changed, 632 insertions(+), 383 deletions(-) create mode 100644 lib/features/base/mixin/email_action_handler_mixin.dart rename lib/features/mailbox_dashboard/presentation/widgets/{ => app_bar}/top_bar_thread_selection.dart (65%) create mode 100644 lib/features/thread/presentation/model/draggable_email_data.dart create mode 100644 lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart diff --git a/core/lib/presentation/resources/assets_paths.dart b/core/lib/presentation/resources/assets_paths.dart index a8c7401165..40734a5628 100644 --- a/core/lib/presentation/resources/assets_paths.dart +++ b/core/lib/presentation/resources/assets_paths.dart @@ -1,5 +1,4 @@ class AssetsPaths { static const images = 'assets/images/'; - static const icons = 'assets/icons/'; static const configurationImages = 'configurations/icons/'; } \ No newline at end of file diff --git a/lib/features/base/mixin/email_action_handler_mixin.dart b/lib/features/base/mixin/email_action_handler_mixin.dart new file mode 100644 index 0000000000..f914b222fd --- /dev/null +++ b/lib/features/base/mixin/email_action_handler_mixin.dart @@ -0,0 +1,37 @@ + +import 'dart:ui'; + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +mixin EmailActionHandlerMixin implements MessageDialogActionMixin{ + Future showConfirmDialogWhenMakeToActionForSelectionAllEmails({ + required ImagePaths imagePaths, + required int totalEmails, + required String folderName, + required VoidCallback onConfirmAction, + }) async { + if (currentContext == null) return; + + final appLocalizations = AppLocalizations.of(currentContext!); + + await showConfirmDialogAction( + currentContext!, + appLocalizations.messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox( + totalEmails, + folderName, + ), + appLocalizations.ok, + title: appLocalizations.confirmBulkAction, + icon: SvgPicture.asset( + imagePaths.icQuotasWarning, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ), + onConfirmAction: onConfirmAction, + ); + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index ac980dd968..b655502877 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -612,6 +612,12 @@ class MailboxController extends BaseMailboxController log('MailboxController::_handleOpenMailbox():MAILBOX_ID = ${presentationMailboxSelected.id.asString} | MAILBOX_NAME: ${presentationMailboxSelected.name?.name}'); KeyboardUtils.hideKeyboard(context); mailboxDashBoardController.clearSelectedEmail(); + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } if (presentationMailboxSelected.id != mailboxDashBoardController.selectedMailbox.value?.id) { mailboxDashBoardController.clearFilterMessageOption(); } diff --git a/lib/features/mailbox/presentation/mailbox_view_web.dart b/lib/features/mailbox/presentation/mailbox_view_web.dart index 0be90fdbf6..6434810aab 100644 --- a/lib/features/mailbox/presentation/mailbox_view_web.dart +++ b/lib/features/mailbox/presentation/mailbox_view_web.dart @@ -14,6 +14,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/widgets/mailbox_load import 'package:tmail_ui_user/features/mailbox/presentation/widgets/user_information_widget.dart'; import 'package:tmail_ui_user/features/quotas/presentation/quotas_view.dart'; import 'package:tmail_ui_user/features/quotas/presentation/styles/quotas_view_styles.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; @@ -393,15 +394,29 @@ class MailboxView extends BaseMailboxView { }).toList() ?? []; } - void _handleDragItemAccepted(List listEmails, PresentationMailbox presentationMailbox) { + void _handleDragItemAccepted( + DraggableEmailData draggableEmailData, + PresentationMailbox presentationMailbox, + ) { final mailboxPath = controller.findNodePath(presentationMailbox.id) ?? presentationMailbox.name?.name; log('MailboxView::_handleDragItemAccepted(): mailboxPath: $mailboxPath'); if (mailboxPath != null) { - final newMailbox = presentationMailbox.toPresentationMailboxWithMailboxPath(mailboxPath); - controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, newMailbox); + presentationMailbox = presentationMailbox + .toPresentationMailboxWithMailboxPath(mailboxPath); + } + + if (draggableEmailData.isSelectAllEmailsEnabled) { + controller + .mailboxDashBoardController + .dragAllSelectedEmailToMailboxAction(presentationMailbox); } else { - controller.mailboxDashBoardController.dragSelectedMultipleEmailToMailboxAction(listEmails, presentationMailbox); + controller + .mailboxDashBoardController + .dragSelectedMultipleEmailToMailboxAction( + draggableEmailData.listEmails!, + presentationMailbox, + ); } } } \ No newline at end of file diff --git a/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart b/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart index 035c1f0771..cc4fcfd877 100644 --- a/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart +++ b/lib/features/mailbox/presentation/utils/mailbox_method_action_define.dart @@ -1,13 +1,13 @@ import 'package:flutter/material.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; typedef OnClickOpenMailboxAction = void Function(PresentationMailbox); typedef OnClickOpenMenuMailboxAction = void Function(RelativeRect, PresentationMailbox); typedef OnSelectMailboxAction = void Function(PresentationMailbox); -typedef OnDragEmailToMailboxAccepted = void Function(List, PresentationMailbox); +typedef OnDragEmailToMailboxAccepted = void Function(DraggableEmailData, PresentationMailbox); typedef OnLongPressMailboxAction = void Function(PresentationMailbox); typedef OnClickExpandMailboxNodeAction = void Function(MailboxNode); diff --git a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart index dba008eb3b..90d278df64 100644 --- a/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart +++ b/lib/features/mailbox/presentation/widgets/mailbox_item_widget.dart @@ -6,7 +6,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/select_mode.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; @@ -16,6 +15,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/styles/mailbox_item_ import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_method_action_define.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/label_mailbox_item_widget.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/widgets/leading_mailbox_item_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; class MailboxItemWidget extends StatefulWidget { @@ -65,8 +65,8 @@ class _MailboxItemWidgetState extends State { @override Widget build(BuildContext context) { if (_responsiveUtils.isWebDesktop(context) && widget.mailboxDisplayed == MailboxDisplayed.mailbox) { - return DragTarget>( - builder: (context, candidateEmails, rejectedEmails) { + return DragTarget( + builder: (context, _, __) { return InkWell( onTap: () => widget.onOpenMailboxFolderClick?.call(widget.mailboxNode), onHover: (value) => setState(() => _isItemHovered = value), @@ -103,7 +103,8 @@ class _MailboxItemWidgetState extends State { ), ); }, - onAcceptWithDetails: (emails) => widget.onDragItemAccepted?.call(emails.data, widget.mailboxNode.item), + onAcceptWithDetails: (details) => + widget.onDragItemAccepted?.call(details.data, widget.mailboxNode.item), ); } else { if (widget.mailboxDisplayed == MailboxDisplayed.mailbox) { diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 047fe16c95..abd3305a9c 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -30,6 +30,7 @@ import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:rxdart/transformers.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/base/mixin/contact_support_mixin.dart'; +import 'package:tmail_ui_user/features/base/mixin/email_action_handler_mixin.dart'; import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart'; import 'package:tmail_ui_user/features/composer/domain/extensions/email_request_extension.dart'; @@ -182,7 +183,9 @@ import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; import 'package:uuid/uuid.dart'; class MailboxDashBoardController extends ReloadableController - with UserSettingPopupMenuMixin, ContactSupportMixin { + with UserSettingPopupMenuMixin, + ContactSupportMixin, + EmailActionHandlerMixin { final RemoveEmailDraftsInteractor _removeEmailDraftsInteractor = Get.find(); final EmailReceiveManager _emailReceiveManager = Get.find(); @@ -255,6 +258,7 @@ class MailboxDashBoardController extends ReloadableController final isRecoveringDeletedMessage = RxBool(false); final localFileDraggableAppState = Rxn(); final isSelectAllEmailsEnabled = RxBool(false); + final isSelectAllPageEnabled = RxBool(false); final markAllAsUnreadSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); final moveAllSelectionAllEmailsViewState = Rx>(Right(UIState.idle)); final deleteAllPermanentlyEmailsViewState = Rx>(Right(UIState.idle)); @@ -811,8 +815,8 @@ class MailboxDashBoardController extends ReloadableController } MailboxId? get spamMailboxId { - return mapDefaultMailboxIdByRole[PresentationMailbox.roleJunk] - ?? mapDefaultMailboxIdByRole[PresentationMailbox.roleSpam]; + return getMailboxIdByRole(PresentationMailbox.roleJunk) + ?? getMailboxIdByRole(PresentationMailbox.roleSpam); } void setMapDefaultMailboxIdByRole(Map newMapMailboxId) { @@ -1224,6 +1228,122 @@ class MailboxDashBoardController extends ReloadableController } } + void dragAllSelectedEmailToMailboxAction(PresentationMailbox destinationMailbox) { + if (selectedMailbox.value == null) return; + + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox.value!.countTotalEmails, + folderName: currentContext != null + ? selectedMailbox.value!.getDisplayName(currentContext!) + : '', + onConfirmAction: () => handleActionsForSelectionAllEmails( + context: currentContext!, + selectedMailbox: selectedMailbox.value!, + actionType: _getTypeMoveAllActionForMailbox(destinationMailbox), + destinationMailbox: destinationMailbox, + ), + ); + } + + EmailActionType _getTypeMoveAllActionForMailbox(PresentationMailbox presentationMailbox) { + if (presentationMailbox.isTrash) { + return EmailActionType.moveAllToTrash; + } else if (presentationMailbox.isSpam) { + return EmailActionType.markAllAsSpam; + } else { + return EmailActionType.moveAll; + } + } + + void handleActionsForSelectionAllEmails({ + required BuildContext context, + required PresentationMailbox selectedMailbox, + required EmailActionType actionType, + PresentationMailbox? destinationMailbox, + }) { + log('MailboxDashBoardController::handleActionsForSelectionAllEmails:actionType = $actionType'); + if (sessionCurrent == null || accountId.value == null) { + logError('MailboxDashBoardController::_handleActionsForSelectionAllEmails: SESSION & ACCOUNT_ID is null'); + return; + } + + switch(actionType) { + case EmailActionType.markAllAsRead: + markAsReadMailbox( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countUnreadEmails, + ); + break; + case EmailActionType.markAllAsUnread: + markAllAsUnreadSelectionAllEmails( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countReadEmails, + ); + break; + case EmailActionType.moveAll: + moveAllSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + destinationMailbox: destinationMailbox, + ); + break; + case EmailActionType.moveAllToTrash: + moveAllToTrashSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + trashMailbox: destinationMailbox, + ); + break; + case EmailActionType.deleteAllPermanently: + deleteAllPermanentlyEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + ); + break; + case EmailActionType.markAllAsStarred: + markAllAsStarredSelectionAllEmails( + sessionCurrent!, + accountId.value!, + selectedMailbox.mailboxId!, + selectedMailbox.getDisplayName(context), + selectedMailbox.countTotalEmails, + ); + break; + case EmailActionType.markAllAsSpam: + maskAllAsSpamSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + spamMailbox: destinationMailbox, + ); + break; + case EmailActionType.allUnSpam: + allUnSpamSelectionAllEmails( + context, + sessionCurrent!, + accountId.value!, + selectedMailbox, + ); + break; + default: + break; + } + } + void dragSelectedMultipleEmailToMailboxAction( List listEmails, PresentationMailbox destinationMailbox, @@ -1246,7 +1366,6 @@ class MailboxDashBoardController extends ReloadableController _handleDragSelectedMultipleEmailToMailboxAction({selectedMailbox.value!.id: listEmails.listEmailIds}, destinationMailbox); } } - } void _handleDragSelectedMultipleEmailToMailboxAction( @@ -2631,6 +2750,7 @@ class MailboxDashBoardController extends ReloadableController } void selectAllEmailAction() { + isSelectAllPageEnabled.value = true; dispatchAction(SelectionAllEmailAction()); } @@ -3107,19 +3227,26 @@ class MailboxDashBoardController extends ReloadableController BuildContext context, Session session, AccountId accountId, - PresentationMailbox currentMailbox + PresentationMailbox currentMailbox, + { + PresentationMailbox? destinationMailbox, + } ) async { - final arguments = DestinationPickerArguments( - accountId, - MailboxActions.moveEmail, - session, - mailboxIdSelected: currentMailbox.id); + if (destinationMailbox == null) { + final arguments = DestinationPickerArguments( + accountId, + MailboxActions.moveEmail, + session, + mailboxIdSelected: currentMailbox.id, + ); - final destinationMailbox = PlatformInfo.isWeb - ? await DialogRouter.pushGeneralDialog( - routeName: AppRoutes.destinationPicker, - arguments: arguments) - : await push(AppRoutes.destinationPicker, arguments: arguments); + destinationMailbox = PlatformInfo.isWeb + ? await DialogRouter.pushGeneralDialog( + routeName: AppRoutes.destinationPicker, + arguments: arguments, + ) + : await push(AppRoutes.destinationPicker, arguments: arguments); + } if (destinationMailbox is PresentationMailbox) { consumeState(_moveAllSelectionAllEmailsInteractor.execute( @@ -3127,7 +3254,9 @@ class MailboxDashBoardController extends ReloadableController accountId, currentMailbox.id, destinationMailbox.id, - destinationMailbox.mailboxPath ?? (context.mounted ? destinationMailbox.getDisplayName(context) : ''), + destinationMailbox.mailboxPath ?? (context.mounted + ? destinationMailbox.getDisplayName(context) + : ''), currentMailbox.countTotalEmails, _moveAllSelectionAllEmailsStreamController, isDestinationSpamMailbox: destinationMailbox.isSpam @@ -3163,13 +3292,21 @@ class MailboxDashBoardController extends ReloadableController BuildContext context, Session session, AccountId accountId, - PresentationMailbox currentMailbox + PresentationMailbox currentMailbox, + { + PresentationMailbox? trashMailbox, + } ) async { - final trashMailboxId = getMailboxIdByRole(PresentationMailbox.roleTrash); + MailboxId? trashMailboxId = trashMailbox?.id; + String? trashMailboxPath = trashMailbox?.getDisplayName(context) ?? ''; - if (trashMailboxId == null) return; + if (trashMailbox == null) { + trashMailboxId = getMailboxIdByRole(PresentationMailbox.roleTrash); + if (trashMailboxId == null) return; + trashMailboxPath = mapMailboxById[trashMailboxId]?.getDisplayName(context) ?? ''; + } - final trashMailboxPath = mapMailboxById[trashMailboxId]?.getDisplayName(context) ?? ''; + if (trashMailboxId == null) return; consumeState(_moveAllSelectionAllEmailsInteractor.execute( session, @@ -3266,13 +3403,21 @@ class MailboxDashBoardController extends ReloadableController BuildContext context, Session session, AccountId accountId, - PresentationMailbox currentMailbox + PresentationMailbox currentMailbox, + { + PresentationMailbox? spamMailbox, + } ) async { - final spamMailboxId = getMailboxIdByRole(PresentationMailbox.roleSpam); + MailboxId? spamMailboxId = spamMailbox?.id; + String spamMailboxPath = spamMailbox?.getDisplayName(context) ?? ''; - if (spamMailboxId == null) return; + if (spamMailbox == null) { + spamMailboxId = this.spamMailboxId; + if (spamMailboxId == null) return; + spamMailboxPath = mapMailboxById[spamMailboxId]?.getDisplayName(context) ?? ''; + } - final spamMailboxPath = mapMailboxById[spamMailboxId]?.getDisplayName(context) ?? ''; + if (spamMailboxId == null) return; consumeState(_moveAllSelectionAllEmailsInteractor.execute( session, diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart index 8a9a310653..f80177716d 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_view_web.dart @@ -34,7 +34,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/re import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/filter_message_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_filters/search_filter_button.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/search_input_form_widget.dart'; -import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart'; +import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/styles/vacation_notification_message_widget_style.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart'; @@ -138,11 +138,21 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { } }), _buildListButtonQuickSearchFilter(context), - Obx(() => MarkMailboxAsReadLoadingWidget(viewState: controller.viewStateMarkAsReadMailbox.value)), - Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value)), - Obx(() => MoveAllSelectionAllEmailsLoadingWidget(viewState: controller.moveAllSelectionAllEmailsViewState.value)), - Obx(() => DeleteAllPermanentlyEmailsLoadingWidget(viewState: controller.deleteAllPermanentlyEmailsViewState.value)), - Obx(() => MarkAllAsStarredSelectionAllEmailsLoadingWidget(viewState: controller.markAllAsStarredSelectionAllEmailsViewState.value)), + Obx(() => MarkMailboxAsReadLoadingWidget( + viewState: controller.viewStateMarkAsReadMailbox.value, + )), + Obx(() => MarkAllAsUnreadSelectionAllEmailsLoadingWidget( + viewState: controller.markAllAsUnreadSelectionAllEmailsViewState.value, + )), + Obx(() => MoveAllSelectionAllEmailsLoadingWidget( + viewState: controller.moveAllSelectionAllEmailsViewState.value, + )), + Obx(() => DeleteAllPermanentlyEmailsLoadingWidget( + viewState: controller.deleteAllPermanentlyEmailsViewState.value, + )), + Obx(() => MarkAllAsStarredSelectionAllEmailsLoadingWidget( + viewState: controller.markAllAsStarredSelectionAllEmailsViewState.value, + )), Expanded(child: Obx(() { switch(controller.dashboardRoute.value) { case DashboardRoutes.thread: @@ -245,22 +255,24 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView { Obx(() { final listEmailSelected = controller.listEmailSelected; if (controller.isSelectionEnabled() && listEmailSelected.isNotEmpty) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.5, horizontal: 16), - child: TopBarThreadSelection( - listEmail: listEmailSelected, - mapMailbox: controller.mapMailboxById, - isSelectAllEmailsEnabled: controller.isSelectAllEmailsEnabled.value, - selectedMailbox: controller.selectedMailbox.value, - onCancelSelection: () => - controller.dispatchAction(CancelSelectionAllEmailAction()), - onEmailActionTypeAction: (listEmails, actionType) => - controller.dispatchAction(HandleEmailActionTypeAction( - listEmails, - actionType - )), - onMoreSelectedEmailAction: (position) => controller.dispatchAction(MoreSelectedEmailAction(context, position)), - ), + return TopBarThreadSelection( + imagePaths: controller.imagePaths, + listEmail: listEmailSelected, + mapMailbox: controller.mapMailboxById, + isSelectAllEmailsEnabled: controller.isSelectAllEmailsEnabled.value, + selectedMailbox: controller.selectedMailbox.value, + onCancelSelection: () => + controller.dispatchAction(CancelSelectionAllEmailAction()), + onEmailActionTypeAction: (listEmails, actionType) => + controller.dispatchAction(HandleEmailActionTypeAction( + listEmails, + actionType, + )), + onMoreSelectedEmailAction: (position) => + controller.dispatchAction(MoreSelectedEmailAction( + context, + position, + )), ); } else { return Padding( diff --git a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart b/lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart similarity index 65% rename from lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart rename to lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart index 739c15f869..fc977262de 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/top_bar_thread_selection.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/app_bar/top_bar_thread_selection.dart @@ -3,7 +3,6 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; -import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; @@ -12,13 +11,15 @@ import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; -typedef OnEmailActionTypeAction = Function(List listEmail, EmailActionType actionType); +typedef OnEmailActionTypeAction = Function( + List listEmail, + EmailActionType actionType, +); typedef OnMoreSelectedEmailAction = Function(RelativeRect position); class TopBarThreadSelection extends StatelessWidget{ - final imagePaths = Get.find(); - + final ImagePaths imagePaths; final List listEmail; final Map mapMailbox; final OnEmailActionTypeAction? onEmailActionTypeAction; @@ -27,8 +28,9 @@ class TopBarThreadSelection extends StatelessWidget{ final PresentationMailbox? selectedMailbox; final OnMoreSelectedEmailAction? onMoreSelectedEmailAction; - TopBarThreadSelection({ + const TopBarThreadSelection({ super.key, + required this.imagePaths, required this.listEmail, required this.mapMailbox, required this.isSelectAllEmailsEnabled, @@ -40,98 +42,100 @@ class TopBarThreadSelection extends StatelessWidget{ @override Widget build(BuildContext context) { - return Row(children: [ - TMailButtonWidget.fromIcon( - icon: imagePaths.icClose, - iconColor: AppColor.primaryColor, - tooltipMessage: AppLocalizations.of(context).cancel, - backgroundColor: Colors.transparent, - iconSize: 28, - padding: const EdgeInsets.all(3), - onTapActionCallback: onCancelSelection - ), - if (!isSelectAllEmailsEnabled) - Padding( - padding: const EdgeInsetsDirectional.only(end: 30), - child: Text( - AppLocalizations.of(context).count_email_selected(listEmail.length), - style: const TextStyle( - fontSize: 17, - fontWeight: FontWeight.w500, - color: AppColor.colorTextButton - ) - ), + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.5, horizontal: 16), + child: Row(children: [ + TMailButtonWidget.fromIcon( + icon: imagePaths.icClose, + iconColor: AppColor.primaryColor, + tooltipMessage: AppLocalizations.of(context).cancel, + backgroundColor: Colors.transparent, + iconSize: 28, + padding: const EdgeInsets.all(3), + onTapActionCallback: onCancelSelection ), - TMailButtonWidget.fromIcon( - icon: _getIconForMarkAsRead(), - tooltipMessage: _getTooltipMessageForMarkAsRead(context), - backgroundColor: Colors.transparent, - iconSize: 24, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - _getActionTypeForMarkAsRead() - ) - ), - TMailButtonWidget.fromIcon( - icon: _getIconForMarkAsStar(), - tooltipMessage: _getTooltipMessageForMarkAsStar(context), - backgroundColor: Colors.transparent, - iconSize: 24, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - _getActionTypeForMarkAsStar() - ) - ), - if (canSpamAndMove) - ...[ - TMailButtonWidget.fromIcon( - icon: imagePaths.icMove, - iconSize: 22, - tooltipMessage: _getTooltipMessageForMove(context), - backgroundColor: Colors.transparent, - onTapActionCallback: () => onEmailActionTypeAction?.call( - List.from(listEmail), - _getActionTypeForMove() - ) - ), - TMailButtonWidget.fromIcon( - icon: _getIconForMoveToSpam(), - backgroundColor: Colors.transparent, - iconSize: 24, - tooltipMessage: _getTooltipMessageForMoveToSpam(context), - onTapActionCallback: () { - onEmailActionTypeAction?.call( - List.from(listEmail.listEmailCanSpam(mapMailbox)), - _getActionTypeForMoveToSpam() - ); - } - ) - ], - if (isAllBelongToTheSameMailbox) + if (!isSelectAllEmailsEnabled) + Padding( + padding: const EdgeInsetsDirectional.only(end: 30), + child: Text( + AppLocalizations.of(context).count_email_selected(listEmail.length), + style: const TextStyle( + fontSize: 17, + fontWeight: FontWeight.w500, + color: AppColor.colorTextButton + ) + ), + ), TMailButtonWidget.fromIcon( - icon: imagePaths.icDeleteComposer, + icon: _getIconForMarkAsRead(), + tooltipMessage: _getTooltipMessageForMarkAsRead(context), backgroundColor: Colors.transparent, - iconSize: 20, - iconColor: _getIconColorForMoveToTrash(), - tooltipMessage: _getTooltipMessageForMoveToTrash(context), - onTapActionCallback: () { - onEmailActionTypeAction?.call( - List.from(listEmail), - _getActionTypeForMoveToTrash(), - ); - } + iconSize: 24, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMarkAsRead() + ) ), - const Spacer(), - if (isSelectAllEmailsEnabled) TMailButtonWidget.fromIcon( - icon: imagePaths.icMoreVertical, - iconSize: 22, - iconColor: AppColor.primaryColor, - tooltipMessage: AppLocalizations.of(context).more, + icon: _getIconForMarkAsStar(), + tooltipMessage: _getTooltipMessageForMarkAsStar(context), backgroundColor: Colors.transparent, - onTapActionAtPositionCallback: onMoreSelectedEmailAction + iconSize: 24, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMarkAsStar() + ) ), - ]); + if (canSpamAndMove) + ...[ + TMailButtonWidget.fromIcon( + icon: imagePaths.icMove, + iconSize: 22, + tooltipMessage: _getTooltipMessageForMove(context), + backgroundColor: Colors.transparent, + onTapActionCallback: () => onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMove() + ) + ), + TMailButtonWidget.fromIcon( + icon: _getIconForMoveToSpam(), + backgroundColor: Colors.transparent, + iconSize: 24, + tooltipMessage: _getTooltipMessageForMoveToSpam(context), + onTapActionCallback: () { + onEmailActionTypeAction?.call( + List.from(listEmail.listEmailCanSpam(mapMailbox)), + _getActionTypeForMoveToSpam() + ); + } + ) + ], + if (isAllBelongToTheSameMailbox) + TMailButtonWidget.fromIcon( + icon: imagePaths.icDeleteComposer, + backgroundColor: Colors.transparent, + iconSize: 20, + iconColor: _getIconColorForMoveToTrash(), + tooltipMessage: _getTooltipMessageForMoveToTrash(context), + onTapActionCallback: () { + onEmailActionTypeAction?.call( + List.from(listEmail), + _getActionTypeForMoveToTrash() + ); + } + ), + if (isSelectAllEmailsEnabled) + TMailButtonWidget.fromIcon( + icon: imagePaths.icMoreVertical, + iconSize: 22, + iconColor: AppColor.primaryColor, + tooltipMessage: AppLocalizations.of(context).more, + backgroundColor: Colors.transparent, + onTapActionAtPositionCallback: onMoreSelectedEmailAction + ), + ]), + ); } bool get canDeletePermanently => listEmail.isAllCanDeletePermanently(mapMailbox); @@ -249,7 +253,6 @@ class TopBarThreadSelection extends StatelessWidget{ } } - EmailActionType _getActionTypeForMarkAsStar() { if (isSelectAllEmailsEnabled) { return EmailActionType.markAllAsStarred; diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart index d9c0a5ec27..b46e3c24b1 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/delete_all_permanently_emails_loading_widget.dart @@ -19,16 +19,16 @@ class DeleteAllPermanentlyEmailsLoadingWidget extends StatelessWidget with AppLo (success) { if (success is DeleteAllPermanentlyEmailsLoading) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalLoadingWidget + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, ); } else if (success is DeleteAllPermanentlyEmailsUpdating) { final percent = success.total > 0 ? success.countDeleted / success.total : 0.0; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalPercentLoadingWidget(percent) + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), ); } return const SizedBox.shrink(); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart index c859468221..53ec1be556 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_starred_selection_all_emails_loading_widget.dart @@ -6,11 +6,15 @@ import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_all_as_starred_selection_all_emails_state.dart'; -class MarkAllAsStarredSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoaderMixin { +class MarkAllAsStarredSelectionAllEmailsLoadingWidget extends StatelessWidget + with AppLoaderMixin { final Either viewState; - const MarkAllAsStarredSelectionAllEmailsLoadingWidget({super.key, required this.viewState}); + const MarkAllAsStarredSelectionAllEmailsLoadingWidget({ + super.key, + required this.viewState, + }); @override Widget build(BuildContext context) { @@ -19,16 +23,16 @@ class MarkAllAsStarredSelectionAllEmailsLoadingWidget extends StatelessWidget wi (success) { if (success is MarkAllAsStarredSelectionAllEmailsLoading) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalLoadingWidget + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, ); } else if (success is MarkAllAsStarredSelectionAllEmailsUpdating) { final percent = success.total > 0 ? success.countStarred / success.total : 0.0; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalPercentLoadingWidget(percent) + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), ); } return const SizedBox.shrink(); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart index 2752e9fb82..2f29c6d0e1 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_all_as_unread_selection_all_emails_loading_widget.dart @@ -19,16 +19,16 @@ class MarkAllAsUnreadSelectionAllEmailsLoadingWidget extends StatelessWidget wit (success) { if (success is MarkAllAsUnreadSelectionAllEmailsLoading) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalLoadingWidget + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, ); } else if (success is MarkAllAsUnreadSelectionAllEmailsUpdating) { final percent = success.totalRead > 0 ? success.countUnread / success.totalRead : 0.0; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalPercentLoadingWidget(percent) + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), ); } return const SizedBox.shrink(); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart index 5926b23db6..73640b7b54 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/mark_mailbox_as_read_loading_widget.dart @@ -19,16 +19,16 @@ class MarkMailboxAsReadLoadingWidget extends StatelessWidget with AppLoaderMixin (success) { if (success is MarkAsMailboxReadLoading) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalLoadingWidget + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, ); } else if (success is UpdatingMarkAsMailboxReadState) { final percent = success.totalUnread > 0 ? success.countRead / success.totalUnread : 0.0; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalPercentLoadingWidget(percent) + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), ); } return const SizedBox.shrink(); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart index 951b8d7ad4..130f7e94a2 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/loading/move_all_selection_all_emails_loading_widget.dart @@ -19,16 +19,16 @@ class MoveAllSelectionAllEmailsLoadingWidget extends StatelessWidget with AppLoa (success) { if (success is MoveAllSelectionAllEmailsLoading) { return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalLoadingWidget + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalLoadingWidget, ); } else if (success is MoveAllSelectionAllEmailsUpdating) { final percent = success.total > 0 ? success.countMoved / success.total : 0.0; return Padding( - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: horizontalPercentLoadingWidget(percent) + padding: const EdgeInsetsDirectional.only(end: 16, bottom: 16), + child: horizontalPercentLoadingWidget(percent), ); } return const SizedBox.shrink(); diff --git a/lib/features/search/mailbox/presentation/search_mailbox_view.dart b/lib/features/search/mailbox/presentation/search_mailbox_view.dart index b388f23fc9..3297f66b05 100644 --- a/lib/features/search/mailbox/presentation/search_mailbox_view.dart +++ b/lib/features/search/mailbox/presentation/search_mailbox_view.dart @@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_action import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_controller.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/utils/search_mailbox_utils.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class SearchMailboxView extends GetWidget @@ -183,7 +184,7 @@ class SearchMailboxView extends GetWidget controller.responsiveUtils, mailboxCurrent, maxWidth: constraints.maxWidth, - onDragEmailToMailboxAccepted: controller.dashboardController.dragSelectedMultipleEmailToMailboxAction, + onDragEmailToMailboxAccepted: _handleDragItemAccepted, onClickOpenMailboxAction: (mailbox) => controller.openMailboxAction(context, mailbox), onClickOpenMenuMailboxAction: (position, mailbox) => _openMailboxMenuAction(context, mailbox, position: position), onLongPressMailboxAction: (mailbox) => _openMailboxMenuAction(context, mailbox), @@ -195,6 +196,24 @@ class SearchMailboxView extends GetWidget }); } + void _handleDragItemAccepted( + DraggableEmailData draggableEmailData, + PresentationMailbox presentationMailbox, + ) { + if (draggableEmailData.isSelectAllEmailsEnabled) { + controller + .dashboardController + .dragAllSelectedEmailToMailboxAction(presentationMailbox); + } else { + controller + .dashboardController + .dragSelectedMultipleEmailToMailboxAction( + draggableEmailData.listEmails!, + presentationMailbox, + ); + } + } + List _listPopupMenuItemAction(BuildContext context, PresentationMailbox mailbox) { final bool subaddressingSupported = MailboxWidgetMixin.isSubaddressingSupported( controller.dashboardController.sessionCurrent, diff --git a/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart b/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart index b6bef14622..253f3dd14e 100644 --- a/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart +++ b/lib/features/search/mailbox/presentation/widgets/mailbox_searched_item_builder.dart @@ -8,12 +8,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:focused_menu_custom/focused_menu.dart'; import 'package:focused_menu_custom/modals.dart'; -import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_method_action_define.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/utils/search_mailbox_utils.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; class MailboxSearchedItemBuilder extends StatefulWidget { @@ -52,10 +52,13 @@ class _MailboxSearchedItemBuilderState extends State @override Widget build(BuildContext context) { if (PlatformInfo.isWeb) { - return DragTarget>( + return DragTarget( builder: (_, __, ___) => _buildMailboxItem(context), - onAcceptWithDetails: (emails) { - widget.onDragEmailToMailboxAccepted?.call(emails.data, widget._presentationMailbox); + onAcceptWithDetails: (details) { + widget.onDragEmailToMailboxAccepted?.call( + details.data, + widget._presentationMailbox, + ); } ); } else { diff --git a/lib/features/thread/data/network/thread_isolate_worker.dart b/lib/features/thread/data/network/thread_isolate_worker.dart index 467afcaff2..1ef9f51b77 100644 --- a/lib/features/thread/data/network/thread_isolate_worker.dart +++ b/lib/features/thread/data/network/thread_isolate_worker.dart @@ -245,6 +245,7 @@ class ThreadIsolateWorker { bool isDestinationSpamMailbox = false } ) async { + log('ThreadIsolateWorker::moveAllSelectionAllEmails: destinationMailboxId = $destinationMailboxId | totalEmails = $totalEmails'); List emailIdListCompleted = List.empty(growable: true); try { bool mailboxHasEmails = true; diff --git a/lib/features/thread/presentation/model/draggable_email_data.dart b/lib/features/thread/presentation/model/draggable_email_data.dart new file mode 100644 index 0000000000..68c1ee0df0 --- /dev/null +++ b/lib/features/thread/presentation/model/draggable_email_data.dart @@ -0,0 +1,22 @@ + +import 'package:equatable/equatable.dart'; +import 'package:model/email/presentation_email.dart'; + +class DraggableEmailData with EquatableMixin { + final List? listEmails; + final bool isSelectAllEmailsEnabled; + + DraggableEmailData({ + this.isSelectAllEmailsEnabled = false, + this.listEmails, + }); + + factory DraggableEmailData.withSelectAllEmails() => + DraggableEmailData(isSelectAllEmailsEnabled: true); + + @override + List get props => [ + listEmails, + isSelectAllEmailsEnabled, + ]; +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index bc83a5a4f3..1368176f89 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -1,6 +1,5 @@ import 'dart:async'; -import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; @@ -8,7 +7,6 @@ import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; @@ -20,6 +18,7 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; +import 'package:tmail_ui_user/features/base/mixin/email_action_handler_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; @@ -95,7 +94,10 @@ import 'package:universal_html/html.dart' as html; typedef StartRangeSelection = int; typedef EndRangeSelection = int; -class ThreadController extends BaseController with EmailActionController, PopupMenuWidgetMixin { +class ThreadController extends BaseController + with EmailActionController, + PopupMenuWidgetMixin, + EmailActionHandlerMixin { final networkConnectionController = Get.find(); @@ -296,7 +298,18 @@ class ThreadController extends BaseController with EmailActionController, PopupM mailboxDashBoardController.clearDashBoardAction(); } else if (action is HandleEmailActionTypeAction) { if (_validateToShowConfirmBulkActionEmailsDialog()) { - _showConfirmDialogWhenMakeToActionForSelectionAllEmails(actionType: action.emailAction); + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox?.countTotalEmails ?? 0, + folderName: currentContext != null + ? selectedMailbox?.getDisplayName(currentContext!) ?? '' + : '', + onConfirmAction: () => mailboxDashBoardController.handleActionsForSelectionAllEmails( + context: currentContext!, + selectedMailbox: selectedMailbox!, + actionType: action.emailAction + ), + ); } else { pressEmailSelectionAction( action.emailAction, @@ -766,6 +779,12 @@ class ThreadController extends BaseController with EmailActionController, PopupM } void selectEmail(PresentationEmail presentationEmailSelected) { + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; if (_rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) { @@ -817,6 +836,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) .toList(); mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + mailboxDashBoardController.isSelectAllPageEnabled.value = false; mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); @@ -1389,6 +1409,12 @@ class ThreadController extends BaseController with EmailActionController, PopupM void handleLoadMoreEmailsRequest() { log('ThreadController::handleLoadMoreEmailsRequest:'); + if (mailboxDashBoardController.isSelectAllPageEnabled.isTrue) { + mailboxDashBoardController.isSelectAllPageEnabled.value = false; + } + if (mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue) { + mailboxDashBoardController.isSelectAllEmailsEnabled.value = false; + } if (isSearchActive) { _searchMoreEmails(); } else { @@ -1397,7 +1423,7 @@ class ThreadController extends BaseController with EmailActionController, PopupM } bool validateToShowSelectionEmailsBanner() { - return mailboxDashBoardController.isSelectionEnabled() && + return mailboxDashBoardController.isSelectAllPageEnabled.isTrue && selectedMailbox != null && selectedMailbox!.countTotalEmails > ThreadConstants.maxCountEmails && mailboxDashBoardController.listEmailSelected.length < @@ -1408,7 +1434,8 @@ class ThreadController extends BaseController with EmailActionController, PopupM final listSelectionEmailActions = [ EmailActionType.markAllAsRead, EmailActionType.markAllAsUnread, - EmailActionType.moveAll, + if (selectedMailbox == null || selectedMailbox?.isDrafts == false) + EmailActionType.moveAll, if (selectedMailbox?.isTrash == true || selectedMailbox?.isSpam == true || selectedMailbox?.isDrafts == true) @@ -1434,7 +1461,16 @@ class ThreadController extends BaseController with EmailActionController, PopupM onCallbackAction: () { popBack(); if (!isSearchActive) { - _showConfirmDialogWhenMakeToActionForSelectionAllEmails(actionType: action); + showConfirmDialogWhenMakeToActionForSelectionAllEmails( + imagePaths: imagePaths, + totalEmails: selectedMailbox?.countTotalEmails ?? 0, + folderName: selectedMailbox?.getDisplayName(context) ?? '', + onConfirmAction: () => mailboxDashBoardController.handleActionsForSelectionAllEmails( + context: context, + selectedMailbox: selectedMailbox!, + actionType: action, + ), + ); } } ) @@ -1445,117 +1481,4 @@ class ThreadController extends BaseController with EmailActionController, PopupM bool _validateToShowConfirmBulkActionEmailsDialog() { return mailboxDashBoardController.isSelectAllEmailsEnabled.isTrue; } - - Future _showConfirmDialogWhenMakeToActionForSelectionAllEmails({ - required EmailActionType actionType, - }) async { - final selectedMailbox = mailboxDashBoardController.selectedMailbox.value; - - if (currentContext == null || selectedMailbox == null) return; - - final appLocalizations = AppLocalizations.of(currentContext!); - final totalEmails = selectedMailbox.totalEmails?.value.value.toInt() ?? 0; - final folderName = selectedMailbox.getDisplayName(currentContext!); - - await showConfirmDialogAction( - currentContext!, - appLocalizations.messageConfirmationDialogWhenMakeToActionForSelectionAllEmailsInMailbox(totalEmails, folderName), - appLocalizations.ok, - title: appLocalizations.confirmBulkAction, - icon: SvgPicture.asset( - imagePaths.icQuotasWarning, - colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), - ), - onConfirmAction: () { - _handleActionsForSelectionAllEmails( - context: currentContext!, - selectedMailbox: selectedMailbox, - actionType: actionType - ); - } - ); - } - - void _handleActionsForSelectionAllEmails({ - required BuildContext context, - required PresentationMailbox selectedMailbox, - required EmailActionType actionType - }) { - if (_session == null || _accountId == null) { - logError('ThreadController::_handleActionsForSelectionAllEmails: SESSION & ACCOUNT_ID is null'); - return; - } - - switch(actionType) { - case EmailActionType.markAllAsRead: - mailboxDashBoardController.markAsReadMailbox( - _session!, - _accountId!, - selectedMailbox.mailboxId!, - selectedMailbox.getDisplayName(context), - selectedMailbox.countUnreadEmails - ); - break; - case EmailActionType.markAllAsUnread: - mailboxDashBoardController.markAllAsUnreadSelectionAllEmails( - _session!, - _accountId!, - selectedMailbox.mailboxId!, - selectedMailbox.getDisplayName(context), - selectedMailbox.countReadEmails - ); - break; - case EmailActionType.moveAll: - mailboxDashBoardController.moveAllSelectionAllEmails( - context, - _session!, - _accountId!, - selectedMailbox, - ); - break; - case EmailActionType.moveAllToTrash: - mailboxDashBoardController.moveAllToTrashSelectionAllEmails( - context, - _session!, - _accountId!, - selectedMailbox, - ); - break; - case EmailActionType.deleteAllPermanently: - mailboxDashBoardController.deleteAllPermanentlyEmails( - context, - _session!, - _accountId!, - selectedMailbox, - ); - break; - case EmailActionType.markAllAsStarred: - mailboxDashBoardController.markAllAsStarredSelectionAllEmails( - _session!, - _accountId!, - selectedMailbox.mailboxId!, - selectedMailbox.getDisplayName(context), - selectedMailbox.countTotalEmails - ); - break; - case EmailActionType.markAllAsSpam: - mailboxDashBoardController.maskAllAsSpamSelectionAllEmails( - context, - _session!, - _accountId!, - selectedMailbox, - ); - break; - case EmailActionType.allUnSpam: - mailboxDashBoardController.allUnSpamSelectionAllEmails( - context, - _session!, - _accountId!, - selectedMailbox, - ); - break; - default: - break; - } - } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 61b7743896..f6a4fdc168 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -22,6 +22,7 @@ import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; +import 'package:tmail_ui_user/features/thread/presentation/model/draggable_email_data.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/scroll_to_top_button_widget_styles.dart'; @@ -31,6 +32,7 @@ import 'package:tmail_ui_user/features/thread/presentation/widgets/app_bar/app_b import 'package:tmail_ui_user/features/thread/presentation/widgets/banner_delete_all_spam_emails_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/banner_empty_trash_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/bottom_bar_thread_selection_widget.dart'; +import 'package:tmail_ui_user/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_builder.dart' if (dart.library.html) 'package:tmail_ui_user/features/thread/presentation/widgets/email_tile_web_builder.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/empty_emails_widget.dart'; @@ -168,10 +170,20 @@ class ThreadView extends GetWidget if (controller.responsiveUtils.isWebDesktop(context)) Obx(() { if (controller.validateToShowSelectionEmailsBanner()) { + final selectedMailbox = controller + .mailboxDashBoardController + .selectedMailbox + .value!; + + final listEmailSelectedLength = controller + .mailboxDashBoardController + .listEmailSelected + .length; + return SelectAllEmailInMailboxBanner( - limitEmailsInPage: controller.mailboxDashBoardController.listEmailSelected.length, - totalEmails: controller.mailboxDashBoardController.selectedMailbox.value!.totalEmails!.value.value.toInt(), - folderName: controller.mailboxDashBoardController.selectedMailbox.value!.getDisplayName(context), + limitEmailsInPage: listEmailSelectedLength, + totalEmails: selectedMailbox.countTotalEmails, + folderName: selectedMailbox.getDisplayName(context), onSelectAllEmailAction: controller.enableSelectAllEmails, onClearSelection: controller.cancelSelectEmail ); @@ -487,13 +499,33 @@ class ThreadView extends GetWidget } Widget _buildEmailItemDraggable(BuildContext context, PresentationEmail presentationEmail) { + final isSelectAllEmailsEnabled = controller + .mailboxDashBoardController + .isSelectAllEmailsEnabled + .value; + return GestureDetector( behavior: HitTestBehavior.translucent, onSecondaryTapDown: (_) {}, onTapDown: (_) {}, - child: Draggable>( - data: controller.listEmailDrag, - feedback: _buildFeedBackWidget(context), + child: Draggable( + data: isSelectAllEmailsEnabled + ? DraggableEmailData.withSelectAllEmails() + : DraggableEmailData(listEmails: controller.listEmailDrag), + feedback: Obx(() { + final isSelectAllEmailsEnabled = controller + .mailboxDashBoardController + .isSelectAllEmailsEnabled + .value; + return DraggableFeedbackWidget( + icon: controller.imagePaths.icFilterMessageAll, + title: isSelectAllEmailsEnabled + ? AppLocalizations.of(context).moveAllConversation + : AppLocalizations.of(context).moveConversation( + controller.listEmailDrag.length, + ), + ); + }), childWhenDragging: _buildEmailItemWhenDragging(context, presentationEmail), dragAnchorStrategy: pointerDragAnchorStrategy, onDragStarted: () { @@ -640,43 +672,6 @@ class ThreadView extends GetWidget } } - Widget _buildFeedBackWidget(BuildContext context) { - return SizedBox( - height: 60, - child: Material( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(10), - color: AppColor.colorTextButton, - child: Padding( - padding: const EdgeInsets.all(16), - child: Row( - children: [ - SvgPicture.asset( - controller.imagePaths.icFilterMessageAll, - width: 24, - height: 24, - fit: BoxFit.fill, - colorFilter: Colors.white.asFilter(), - ), - const SizedBox(width: 10), - Obx( - () => Text( - AppLocalizations.of(context).moveConversation(controller.listEmailDrag.length), - overflow: TextOverflow.clip, - style: const TextStyle( - color: Colors.white, - fontWeight: FontWeight.bold, - ), - maxLines: 1, - ), - ), - ], - ), - ), - ), - ); - } - Widget _buildEmptyEmail(BuildContext context) { return Obx(() => controller.viewState.value.fold( (failure) => const SizedBox.shrink(), diff --git a/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart b/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart new file mode 100644 index 0000000000..9932d77892 --- /dev/null +++ b/lib/features/thread/presentation/widgets/draggable/draggable_feedback_widget.dart @@ -0,0 +1,52 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +class DraggableFeedbackWidget extends StatelessWidget { + + final String icon; + final String title; + + const DraggableFeedbackWidget({ + super.key, + required this.icon, + required this.title, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 60, + child: Material( + clipBehavior: Clip.hardEdge, + borderRadius: const BorderRadius.all(Radius.circular(10)), + color: AppColor.colorTextButton, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + SvgPicture.asset( + icon, + width: 24, + height: 24, + fit: BoxFit.fill, + colorFilter: Colors.white.asFilter(), + ), + const SizedBox(width: 10), + Text( + title, + overflow: TextOverflow.clip, + style: const TextStyle( + color: Colors.white, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart index 0b0390dd3a..c4d896d296 100644 --- a/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_all_email_in_mailbox_widget.dart @@ -27,18 +27,7 @@ class MessageSelectAllEmailInMailboxWidget extends StatelessWidget { ), children: [ TextSpan( - text: AppLocalizations.of(context).all, - ), - TextSpan( - text: ' $totalEmails ', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - TextSpan( - text: AppLocalizations.of(context).mailsInMailboxAreSelected(folderName), + text: '${AppLocalizations.of(context).mailsInMailboxAreSelected(totalEmails, folderName)} ', ), TextSpan( text: AppLocalizations.of(context).clearSelection, @@ -48,9 +37,9 @@ class MessageSelectAllEmailInMailboxWidget extends StatelessWidget { fontSize: 14, ), recognizer: TapGestureRecognizer()..onTap = onClearSelection - ) - ] - ) + ), + ], + ), ); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart index db6f4180cd..9103b13514 100644 --- a/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart +++ b/lib/features/thread/presentation/widgets/select_all_banner/message_select_email_in_page_widget.dart @@ -29,18 +29,7 @@ class MessageSelectEmailInPageWidget extends StatelessWidget { ), children: [ TextSpan( - text: AppLocalizations.of(context).all, - ), - TextSpan( - text: ' $limitEmailsInPage ', - style: Theme.of(context).textTheme.bodySmall?.copyWith( - color: Colors.black, - fontWeight: FontWeight.bold, - fontSize: 14, - ), - ), - TextSpan( - text: AppLocalizations.of(context).mailsOnThisPageAreSelected, + text: '${AppLocalizations.of(context).mailsOnThisPageAreSelected(limitEmailsInPage)} ', ), TextSpan( text: AppLocalizations.of(context).selectAllMailInMailbox( @@ -53,9 +42,9 @@ class MessageSelectEmailInPageWidget extends StatelessWidget { fontSize: 14, ), recognizer: TapGestureRecognizer()..onTap = onSelectAllEmailAction - ) - ] - ) + ), + ], + ), ); } } \ No newline at end of file diff --git a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart index 3b6c7cad3e..6ad0a06583 100644 --- a/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart +++ b/lib/features/thread/presentation/widgets/select_all_banner/select_all_emails_in_mailbox_banner.dart @@ -36,7 +36,7 @@ class _SelectAllEmailInMailboxBannerState extends State