From e7bf7f782e92ab3088d17b2973c676db361a669d Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 15 Sep 2021 11:02:56 +0700 Subject: [PATCH] TF-78 Implement mark as read & unread for multiple selected email in threads --- .../mark_as_multiple_email_read_state.dart | 38 +++++++++++++ ...ark_as_multiple_email_read_interactor.dart | 37 +++++++++++++ .../thread/presentation/thread_bindings.dart | 16 ++++++ .../presentation/thread_controller.dart | 54 +++++++++++++++++-- lib/l10n/intl_en.arb | 19 +++++++ lib/l10n/intl_fr.arb | 19 +++++++ lib/l10n/intl_messages.arb | 21 +++++++- lib/l10n/intl_ru.arb | 19 +++++++ lib/l10n/intl_vi.arb | 19 +++++++ lib/main/localizations/app_localizations.dart | 23 ++++++++ model/lib/email/presentation_email.dart | 2 + 11 files changed, 262 insertions(+), 5 deletions(-) create mode 100644 lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart create mode 100644 lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart diff --git a/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart b/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart new file mode 100644 index 000000000..67bc56927 --- /dev/null +++ b/lib/features/thread/domain/state/mark_as_multiple_email_read_state.dart @@ -0,0 +1,38 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; + +class MarkAsMultipleEmailReadAllSuccess extends UIState { + final List> resultList; + + MarkAsMultipleEmailReadAllSuccess(this.resultList); + + @override + List get props => [resultList]; +} + +class MarkAsMultipleEmailReadAllFailure extends FeatureFailure { + final List> resultList; + + MarkAsMultipleEmailReadAllFailure(this.resultList); + + @override + List get props => [resultList]; +} + +class MarkAsMultipleEmailReadHasSomeEmailFailure extends UIState { + final List> resultList; + + MarkAsMultipleEmailReadHasSomeEmailFailure(this.resultList); + + @override + List get props => [resultList]; +} + +class MarkAsMultipleEmailReadFailure extends FeatureFailure { + final exception; + + MarkAsMultipleEmailReadFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart new file mode 100644 index 000000000..fa49448b7 --- /dev/null +++ b/lib/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart @@ -0,0 +1,37 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; +import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; + +class MarkAsMultipleEmailReadInteractor { + final MarkAsEmailReadInteractor markAsEmailReadInteractor; + + MarkAsMultipleEmailReadInteractor(this.markAsEmailReadInteractor); + + Future> execute(AccountId accountId, List listEmailId, bool unread) async { + try { + final listResult = await Future.wait(listEmailId.map((emailId) => + markAsEmailReadInteractor.execute(accountId, emailId, unread))); + if (listResult.length == 1) { + return listResult.first; + } else { + var failedFileCount = 0; + listResult.forEach((element) { + if (element is Left) { + failedFileCount++; + } + }); + if (failedFileCount == 0) { + return Right(MarkAsMultipleEmailReadAllSuccess(listResult)); + } else if (failedFileCount == listResult.length) { + return Left(MarkAsMultipleEmailReadAllFailure(listResult)); + } + return Right(MarkAsMultipleEmailReadHasSomeEmailFailure(listResult)); + } + } catch (e) { + return Left(MarkAsMultipleEmailReadFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_bindings.dart b/lib/features/thread/presentation/thread_bindings.dart index 82adc1713..68e3dd20a 100644 --- a/lib/features/thread/presentation/thread_bindings.dart +++ b/lib/features/thread/presentation/thread_bindings.dart @@ -1,12 +1,20 @@ +import 'package:core/core.dart'; import 'package:core/presentation/utils/responsive_utils.dart'; import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/data/datasource/thread_datasource.dart'; import 'package:tmail_ui_user/features/thread/data/datasource_impl/thread_datasource_impl.dart'; import 'package:tmail_ui_user/features/thread/data/network/thread_api.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/get_emails_in_mailbox_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/presentation/thread_controller.dart'; class ThreadBindings extends Bindings { @@ -18,10 +26,18 @@ class ThreadBindings extends Bindings { Get.lazyPut(() => Get.find()); Get.lazyPut(() => GetEmailsInMailboxInteractor(Get.find())); Get.lazyPut(() => ScrollController()); + Get.lazyPut(() => EmailDataSourceImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => EmailRepositoryImpl(Get.find())); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find())); + Get.lazyPut(() => MarkAsMultipleEmailReadInteractor(Get.find())); Get.put(ThreadController( Get.find(), Get.find(), Get.find(), + Get.find(), + Get.find(), )); } } \ 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 f17f80f09..30db02f8c 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -16,11 +16,14 @@ import 'package:jmap_dart_client/jmap/mail/email/email_filter_condition.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/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_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/usecases/get_emails_in_mailbox_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/presentation/model/load_more_state.dart'; import 'package:tmail_ui_user/features/thread/presentation/widgets/email_context_menu_action_builder.dart'; import 'package:tmail_ui_user/main/actions/email_action.dart'; @@ -32,6 +35,8 @@ class ThreadController extends BaseController { final mailboxDashBoardController = Get.find(); final GetEmailsInMailboxInteractor _getEmailsInMailboxInteractor; + final MarkAsMultipleEmailReadInteractor _markAsMultipleEmailReadInteractor; + final AppToast _appToast; final ResponsiveUtils responsiveUtils; final ScrollController listEmailController; @@ -52,6 +57,8 @@ class ThreadController extends BaseController { this.responsiveUtils, this._getEmailsInMailboxInteractor, this.listEmailController, + this._markAsMultipleEmailReadInteractor, + this._appToast, ); @override @@ -204,7 +211,7 @@ class ThreadController extends BaseController { bool _isUnSelectedAll() => emailList.every((email) => email.selectMode == SelectMode.INACTIVE); - bool _isEmailAllRead(List listEmail) => listEmail.every((email) => !email.isUnReadEmail()); + bool _isEmailAllRead(List listEmail) => listEmail.every((email) => email.isReadEmail()); void cancelSelectEmail() { emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); @@ -218,8 +225,47 @@ class ThreadController extends BaseController { emailList.value = newEmailList; } - void unreadSelectedEmail(List listEmail) { - popBack(); + void unreadSelectedEmail(List listEmail, {bool fromContextMenuAction = false}) { + if (fromContextMenuAction) { + popBack(); + } + + final isUnread = _isEmailAllRead(listEmail); + + final listEmailId = listEmail + .where((email) => isUnread ? email.isReadEmail() : email.isUnReadEmail()) + .map((email) => email.id) + .toList(); + + final accountId = mailboxDashBoardController.accountId.value; + + if (accountId != null) { + _markAsMultipleEmailReadInteractor + .execute(accountId, listEmailId, isUnread) + .then((result) => result.fold( + (failure) { + cancelSelectEmail(); + + if (failure is MarkAsEmailReadFailure + || failure is MarkAsMultipleEmailReadAllFailure + || failure is MarkAsMultipleEmailReadFailure) { + _appToast.showErrorToast(AppLocalizations.of(Get.context!).an_error_occurred); + } + }, + (success) { + cancelSelectEmail(); + + if (success is MarkAsEmailReadSuccess + || success is MarkAsMultipleEmailReadAllSuccess + || success is MarkAsMultipleEmailReadHasSomeEmailFailure) { + if (Get.context != null) { + _appToast.showSuccessToast(isUnread + ? AppLocalizations.of(Get.context!).marked_multiple_item_as_unread(listEmail.length) + : AppLocalizations.of(Get.context!).marked_multiple_item_as_read(listEmail.length)); + } + } + })); + } } void openContextMenuSelectedEmail(BuildContext context, ImagePaths imagePaths, List listEmail) { @@ -245,7 +291,7 @@ class ThreadController extends BaseController { SvgPicture.asset(imagePaths.icEyeDisable, width: 24, height: 24, fit: BoxFit.fill), _isEmailAllRead(listEmail) ? AppLocalizations.of(context).mark_as_unread : AppLocalizations.of(context).mark_as_read, listEmail) - ..onActionClick((data) => unreadSelectedEmail(data))) + ..onActionClick((data) => unreadSelectedEmail(data, fromContextMenuAction: true))) .build(); } diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 3519fd2b1..860655f45 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -199,5 +199,24 @@ "@move_to_spam": { "type": "text", "placeholders": {} + }, + "marked_multiple_item_as_read": "Marked {count} item as read", + "@marked_multiple_item_as_read": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "marked_multiple_item_as_unread": "Marked {count} item as unread", + "@marked_multiple_item_as_unread": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "an_error_occurred": "Error! An error occurred. Please try again later.", + "@an_error_occurred": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_fr.arb b/lib/l10n/intl_fr.arb index 3519fd2b1..860655f45 100644 --- a/lib/l10n/intl_fr.arb +++ b/lib/l10n/intl_fr.arb @@ -199,5 +199,24 @@ "@move_to_spam": { "type": "text", "placeholders": {} + }, + "marked_multiple_item_as_read": "Marked {count} item as read", + "@marked_multiple_item_as_read": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "marked_multiple_item_as_unread": "Marked {count} item as unread", + "@marked_multiple_item_as_unread": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "an_error_occurred": "Error! An error occurred. Please try again later.", + "@an_error_occurred": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 3a8c15b2e..bfa2ffaf0 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2021-09-15T10:03:14.927099", + "@@last_modified": "2021-09-15T11:00:57.061103", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -254,5 +254,24 @@ "@move_to_spam": { "type": "text", "placeholders": {} + }, + "marked_multiple_item_as_read": "Marked {count} item as read", + "@marked_multiple_item_as_read": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "marked_multiple_item_as_unread": "Marked {count} item as unread", + "@marked_multiple_item_as_unread": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "an_error_occurred": "Error! An error occurred. Please try again later.", + "@an_error_occurred": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_ru.arb b/lib/l10n/intl_ru.arb index 3519fd2b1..860655f45 100644 --- a/lib/l10n/intl_ru.arb +++ b/lib/l10n/intl_ru.arb @@ -199,5 +199,24 @@ "@move_to_spam": { "type": "text", "placeholders": {} + }, + "marked_multiple_item_as_read": "Marked {count} item as read", + "@marked_multiple_item_as_read": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "marked_multiple_item_as_unread": "Marked {count} item as unread", + "@marked_multiple_item_as_unread": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "an_error_occurred": "Error! An error occurred. Please try again later.", + "@an_error_occurred": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/l10n/intl_vi.arb b/lib/l10n/intl_vi.arb index 3519fd2b1..860655f45 100644 --- a/lib/l10n/intl_vi.arb +++ b/lib/l10n/intl_vi.arb @@ -199,5 +199,24 @@ "@move_to_spam": { "type": "text", "placeholders": {} + }, + "marked_multiple_item_as_read": "Marked {count} item as read", + "@marked_multiple_item_as_read": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "marked_multiple_item_as_unread": "Marked {count} item as unread", + "@marked_multiple_item_as_unread": { + "type": "text", + "placeholders": { + "count": {} + } + }, + "an_error_occurred": "Error! An error occurred. Please try again later.", + "@an_error_occurred": { + "type": "text", + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index ac6289179..79ce07ce3 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -277,5 +277,28 @@ class AppLocalizations { name: 'move_to_spam', ); } + + String marked_multiple_item_as_read(int count) { + return Intl.message( + 'Marked $count item as read', + name: 'marked_multiple_item_as_read', + args: [count] + ); + } + + String marked_multiple_item_as_unread(int count) { + return Intl.message( + 'Marked $count item as unread', + name: 'marked_multiple_item_as_unread', + args: [count] + ); + } + + String get an_error_occurred { + return Intl.message( + 'Error! An error occurred. Please try again later.', + name: 'an_error_occurred', + ); + } } diff --git a/model/lib/email/presentation_email.dart b/model/lib/email/presentation_email.dart index f114e5de8..419dc7eb7 100644 --- a/model/lib/email/presentation_email.dart +++ b/model/lib/email/presentation_email.dart @@ -64,6 +64,8 @@ class PresentationEmail with EquatableMixin { bool isUnReadEmail() => !(keywords?.containsKey(KeyWordIdentifier.emailSeen) == true); + bool isReadEmail() => keywords?.containsKey(KeyWordIdentifier.emailSeen) == true; + bool isFlaggedEmail() => keywords?.containsKey(KeyWordIdentifier.emailFlagged) == true; @override