From afc8c0fe777c3a732a5b943e7197c672976e1cc1 Mon Sep 17 00:00:00 2001 From: dab246 Date: Tue, 21 Sep 2021 01:26:50 +0700 Subject: [PATCH] TF-80 Move email to Mailbox in Email Detailed view --- .../destination_picker_controller.dart | 2 +- .../model/destination_picker_arguments.dart | 8 ++--- .../data/datasource/email_datasource.dart | 3 ++ .../email_datasource_impl.dart | 10 ++++++ .../email/data/network/email_api.dart | 29 +++++++++++++++ .../repository/email_repository_impl.dart | 6 ++++ .../email/domain/model/move_request.dart | 34 ++++++++++++++++++ .../domain/repository/email_repository.dart | 3 ++ .../domain/state/move_to_mailbox_state.dart | 20 +++++++++++ .../usecases/move_to_mailbox_interactor.dart | 23 ++++++++++++ .../email/presentation/email_bindings.dart | 5 ++- .../email/presentation/email_controller.dart | 36 ++++++++++++++++--- .../email/presentation/email_view.dart | 2 +- .../presentation/thread_controller.dart | 20 ++++++++--- lib/main/localizations/app_localizations.dart | 15 ++++++++ model/lib/email/move_action.dart | 5 +++ .../lib/extensions/mailbox_id_extension.dart | 15 ++++++++ model/lib/model.dart | 2 ++ 18 files changed, 222 insertions(+), 16 deletions(-) create mode 100644 lib/features/email/domain/model/move_request.dart create mode 100644 lib/features/email/domain/state/move_to_mailbox_state.dart create mode 100644 lib/features/email/domain/usecases/move_to_mailbox_interactor.dart create mode 100644 model/lib/email/move_action.dart create mode 100644 model/lib/extensions/mailbox_id_extension.dart diff --git a/lib/features/destination_picker/presentation/destination_picker_controller.dart b/lib/features/destination_picker/presentation/destination_picker_controller.dart index 19bd2e3d7..2fb6d376e 100644 --- a/lib/features/destination_picker/presentation/destination_picker_controller.dart +++ b/lib/features/destination_picker/presentation/destination_picker_controller.dart @@ -63,7 +63,7 @@ class DestinationPickerController extends BaseController { } void moveEmailToMailboxAction(PresentationMailbox destinationMailbox) { - popBack(); + popBack(result: destinationMailbox); } void closeDestinationPicker() { diff --git a/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart b/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart index c666d445f..852460eb7 100644 --- a/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart +++ b/lib/features/destination_picker/presentation/model/destination_picker_arguments.dart @@ -2,15 +2,15 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; -import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; class DestinationPickerArguments with EquatableMixin{ final AccountId accountId; final List emailIds; - final MailboxId currentMailboxId; + final PresentationMailbox currentMailbox; - DestinationPickerArguments(this.accountId, this.emailIds, this.currentMailboxId); + DestinationPickerArguments(this.accountId, this.emailIds, this.currentMailbox); @override - List get props => [accountId, emailIds, currentMailboxId]; + List get props => [accountId, emailIds, currentMailbox]; } \ No newline at end of file diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index c33bc820f..d2f932f4c 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/email/read_actions.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; abstract class EmailDataSource { Future getEmailContent(AccountId accountId, EmailId emailId); @@ -26,4 +27,6 @@ abstract class EmailDataSource { AccountRequest accountRequest, CancelToken cancelToken ); + + Future moveToMailbox(AccountId accountId, MoveRequest moveRequest); } \ No newline at end of file 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 047bbfcbb..2fbf04712 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -5,6 +5,7 @@ import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/network/email_api.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; class EmailDataSourceImpl extends EmailDataSource { @@ -67,4 +68,13 @@ class EmailDataSourceImpl extends EmailDataSource { throw error; }); } + + @override + Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) { + return Future.sync(() async { + return await emailAPI.moveToMailbox(accountId, moveRequest); + }).catchError((error) { + throw error; + }); + } } \ 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 5d4bd7980..b35bce6b2 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -26,6 +26,7 @@ import 'package:jmap_dart_client/jmap/mail/email/submission/set/set_email_submis import 'package:path_provider/path_provider.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; class EmailAPI { @@ -186,4 +187,32 @@ class EmailAPI { accountRequest.basicAuth, cancelToken: cancelToken); } + + Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) async { + final setEmailMethod = SetEmailMethod(accountId) + ..addUpdates({ + moveRequest.emailId.id: moveRequest.currentMailboxId + .generateMoveToMailboxActionPath(moveRequest.destinationMailboxId) + }); + + final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); + + final setEmailInvocation = requestBuilder.invocation(setEmailMethod); + + final response = await (requestBuilder + ..usings(setEmailMethod.requiredCapabilities)) + .build() + .execute(); + + final setEmailResponse = response.parse( + setEmailInvocation.methodCallId, + SetEmailResponse.deserialize); + + return Future.sync(() async { + final emailUpdated = setEmailResponse!.updated![moveRequest.emailId.id]; + return emailUpdated == null; + }).catchError((error) { + throw error; + }); + } } \ No newline at end of file diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 93ccf915b..3299f99b1 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -5,6 +5,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; class EmailRepositoryImpl extends EmailRepository { @@ -53,4 +54,9 @@ class EmailRepositoryImpl extends EmailRepository { accountRequest, cancelToken); } + + @override + Future moveToMailbox(AccountId accountId, MoveRequest moveRequest) { + return emailDataSource.moveToMailbox(accountId, moveRequest); + } } \ No newline at end of file diff --git a/lib/features/email/domain/model/move_request.dart b/lib/features/email/domain/model/move_request.dart new file mode 100644 index 000000000..e90f6013d --- /dev/null +++ b/lib/features/email/domain/model/move_request.dart @@ -0,0 +1,34 @@ + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/model.dart'; + +class MoveRequest with EquatableMixin { + + final EmailId emailId; + final MailboxId currentMailboxId; + final MailboxName currentMailboxName; + final MailboxId destinationMailboxId; + final MailboxName destinationMailboxName; + final MoveAction moveAction; + + MoveRequest( + this.emailId, + this.currentMailboxId, + this.currentMailboxName, + this.destinationMailboxId, + this.destinationMailboxName, + this.moveAction, + ); + + @override + List get props => [ + emailId, + currentMailboxId, + currentMailboxName, + destinationMailboxId, + destinationMailboxName, + moveAction + ]; +} \ No newline at end of file diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index cdb2d25f5..dccd09415 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -3,6 +3,7 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; abstract class EmailRepository { Future getEmailContent(AccountId accountId, EmailId emailId); @@ -25,4 +26,6 @@ abstract class EmailRepository { AccountRequest accountRequest, CancelToken cancelToken ); + + Future moveToMailbox(AccountId accountId, MoveRequest moveRequest); } \ No newline at end of file diff --git a/lib/features/email/domain/state/move_to_mailbox_state.dart b/lib/features/email/domain/state/move_to_mailbox_state.dart new file mode 100644 index 000000000..06371470b --- /dev/null +++ b/lib/features/email/domain/state/move_to_mailbox_state.dart @@ -0,0 +1,20 @@ +import 'package:core/core.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; + +class MoveToMailboxSuccess extends UIState { + final MoveRequest moveRequest; + + MoveToMailboxSuccess(this.moveRequest); + + @override + List get props => [moveRequest]; +} + +class MoveToMailboxFailure extends FeatureFailure { + final exception; + + MoveToMailboxFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart b/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart new file mode 100644 index 000000000..af47f7f6d --- /dev/null +++ b/lib/features/email/domain/usecases/move_to_mailbox_interactor.dart @@ -0,0 +1,23 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; + +class MoveToMailboxInteractor { + final EmailRepository emailRepository; + + MoveToMailboxInteractor(this.emailRepository); + + Stream> execute(AccountId accountId, MoveRequest moveRequest) async* { + try { + final result = await emailRepository.moveToMailbox(accountId, moveRequest); + yield result + ? Right(MoveToMailboxSuccess(moveRequest)) + : Left(MoveToMailboxFailure(null)); + } catch (e) { + yield Left(MoveToMailboxFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/email/presentation/email_bindings.dart b/lib/features/email/presentation/email_bindings.dart index aced7f897..5d2ef7a84 100644 --- a/lib/features/email/presentation/email_bindings.dart +++ b/lib/features/email/presentation/email_bindings.dart @@ -11,6 +11,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/email_controller.dart'; import 'package:tmail_ui_user/features/login/data/repository/credential_repository_impl.dart'; import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart'; @@ -33,12 +34,14 @@ class EmailBindings extends Bindings { Get.lazyPut(() => ExportAttachmentInteractor( Get.find(), Get.find())); + Get.lazyPut(() => MoveToMailboxInteractor(Get.find())); Get.put(EmailController( Get.find(), Get.find(), Get.find(), Get.find(), Get.find(), - Get.find())); + Get.find(), + Get.find())); } } \ No newline at end of file diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index e07a390fe..2768308d1 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -8,18 +8,22 @@ import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_request.dart'; import 'package:tmail_ui_user/features/email/domain/state/download_attachments_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/export_attachment_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/download_attachments_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/export_attachment_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.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/main/localizations/app_localizations.dart'; @@ -38,6 +42,7 @@ class EmailController extends BaseController { final DeviceManager _deviceManager; final AppToast _appToast; final ExportAttachmentInteractor _exportAttachmentInteractor; + final MoveToMailboxInteractor _moveToMailboxInteractor; final emailAddressExpandMode = ExpandMode.COLLAPSE.obs; final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; @@ -50,6 +55,7 @@ class EmailController extends BaseController { this._deviceManager, this._appToast, this._exportAttachmentInteractor, + this._moveToMailboxInteractor, ); @override @@ -101,6 +107,8 @@ class EmailController extends BaseController { _markAsEmailReadSuccess(success); } else if (success is ExportAttachmentSuccess) { _exportAttachmentSuccessAction(success); + } else if (success is MoveToMailboxSuccess) { + _moveToMailboxSuccess(success); } }); } @@ -231,18 +239,36 @@ class EmailController extends BaseController { } } - void openDestinationPickerView(PresentationEmail email) { - final currentMailboxId = mailboxDashBoardController.selectedMailbox.value?.id; + void openDestinationPickerView(PresentationEmail email) async { + final currentMailbox = mailboxDashBoardController.selectedMailbox.value; final accountId = mailboxDashBoardController.accountId.value; - if (currentMailboxId != null && accountId != null) { - push( + if (currentMailbox != null && accountId != null) { + final mailboxDestination = await push( AppRoutes.DESTINATION_PICKER, - arguments: DestinationPickerArguments(accountId, [email.id], currentMailboxId) + arguments: DestinationPickerArguments(accountId, [email.id], currentMailbox) ); + + if (mailboxDestination != null && mailboxDestination is PresentationMailbox) { + _moveToMailbox(accountId, MoveRequest( + email.id, + currentMailbox.id, + currentMailbox.name ?? MailboxName(''), + mailboxDestination.id, + mailboxDestination.name ?? MailboxName(''), + MoveAction.moveTo)); + } } } + void _moveToMailbox(AccountId accountId, MoveRequest moveRequest) { + consumeState(_moveToMailboxInteractor.execute(accountId, moveRequest)); + } + + void _moveToMailboxSuccess(Success success) { + mailboxDashBoardController.dispatchState(Right(success)); + } + bool canComposeEmail() => mailboxDashBoardController.sessionCurrent != null && mailboxDashBoardController.userProfile.value != null && mailboxDashBoardController.mapMailboxId.containsKey(PresentationMailbox.roleOutbox) diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index d27d629f0..cc2328031 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -17,7 +17,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_rec import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:filesize/filesize.dart'; -class EmailView extends GetWidget { +class EmailView extends GetView { final emailController = Get.find(); final responsiveUtils = Get.find(); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 08eecc300..ddefe9bc1 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -17,6 +17,7 @@ import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; +import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_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'; @@ -77,16 +78,26 @@ class ThreadController extends BaseController { if (success is MarkAsEmailReadSuccess || success is MarkAsMultipleEmailReadAllSuccess || success is MarkAsMultipleEmailReadHasSomeEmailFailure) { - _refreshListEmailMarkAsRead(); + _refreshListEmail(); } }); } }); + + mailboxDashBoardController.viewState.listen((state) { + state.map((success) { + if (success is MoveToMailboxSuccess) { + _refreshListEmail(); + mailboxDashBoardController.clearState(); + } + }); + }); } @override void onClose() { mailboxDashBoardController.selectedMailbox.close(); + mailboxDashBoardController.viewState.close(); listEmailController.dispose(); super.onClose(); } @@ -233,7 +244,7 @@ class ThreadController extends BaseController { currentSelectMode.value = SelectMode.INACTIVE; } - void _refreshListEmailMarkAsRead() { + void _refreshListEmail() { final newLimit = emailList.isNotEmpty ? UnsignedInt(emailList.length) : ThreadConstants.defaultLimit; loadMoreState.value = LoadMoreState.IDLE; emailList.clear(); @@ -320,14 +331,15 @@ class ThreadController extends BaseController { } void moveSelectedMultipleEmailToMailboxAction(List listEmail) { + final currentMailbox = mailboxDashBoardController.selectedMailbox.value; final accountId = mailboxDashBoardController.accountId.value; - if (_currentMailboxId != null && accountId != null) { + if (currentMailbox != null && accountId != null) { popBack(); final listEmailIds = listEmail.map((email) => email.id).toList(); push( AppRoutes.DESTINATION_PICKER, - arguments: DestinationPickerArguments(accountId, listEmailIds, _currentMailboxId!) + arguments: DestinationPickerArguments(accountId, listEmailIds, currentMailbox) ); } } diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 57b41d4d8..301d49aa3 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -393,5 +393,20 @@ class AppLocalizations { name: 'browse', ); } + + String moved_to_mailbox(String destinationMailboxPath) { + return Intl.message( + 'Moved to $destinationMailboxPath', + name: 'moved_to_mailbox', + args: [destinationMailboxPath] + ); + } + + String get undo_action { + return Intl.message( + 'UNDO', + name: 'undo_action' + ); + } } diff --git a/model/lib/email/move_action.dart b/model/lib/email/move_action.dart new file mode 100644 index 000000000..ce4376e55 --- /dev/null +++ b/model/lib/email/move_action.dart @@ -0,0 +1,5 @@ + +enum MoveAction { + moveTo, + undo +} \ No newline at end of file diff --git a/model/lib/extensions/mailbox_id_extension.dart b/model/lib/extensions/mailbox_id_extension.dart new file mode 100644 index 000000000..497921463 --- /dev/null +++ b/model/lib/extensions/mailbox_id_extension.dart @@ -0,0 +1,15 @@ +import 'package:jmap_dart_client/jmap/core/patch_object.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; + +extension MailboxIdExtension on MailboxId { + String generatePath() { + return '${PatchObject.mailboxIdsProperty}/${id.value}'; + } + + PatchObject generateMoveToMailboxActionPath(MailboxId destinationMailboxId) { + return PatchObject({ + generatePath(): null, + destinationMailboxId.generatePath(): true + }); + } +} \ No newline at end of file diff --git a/model/lib/model.dart b/model/lib/model.dart index 9dccc63b6..4dac31ce4 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -24,6 +24,7 @@ export 'email/presentation_email_address.dart'; export 'email/email_address_cache.dart'; export 'email/read_actions.dart'; export 'email/attachment.dart'; +export 'email/move_action.dart'; // Extensions export 'extensions/email_address_extension.dart'; @@ -37,6 +38,7 @@ export 'extensions/keyword_identifier_extension.dart'; export 'extensions/presentation_mailbox_extension.dart'; export 'extensions/email_body_part_extension.dart'; export 'extensions/attachment_extension.dart'; +export 'extensions/mailbox_id_extension.dart'; // Download export 'download/download_task_id.dart';