diff --git a/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart b/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart index 3b672207a..77c74ae59 100644 --- a/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart +++ b/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart @@ -5,7 +5,6 @@ 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/email/email.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; -import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.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'; @@ -27,19 +26,14 @@ class UpdateEmailDraftsInteractor { final currentMailboxState = listState.first; final currentEmailState = listState.last; - final newEmailDrafts = await _emailRepository.updateEmailDrafts(session, accountId, newEmail); - final emailDeleted = await _emailRepository.deleteEmailPermanently(session, accountId, oldEmailId); - if (emailDeleted) { - yield Right( - UpdateEmailDraftsSuccess( - newEmailDrafts, - currentEmailState: currentEmailState, - currentMailboxState: currentMailboxState - ) - ); - } else { - yield Left(UpdateEmailDraftsFailure(CannotDeleteOldEmailException())); - } + final newEmailDrafts = await _emailRepository.updateEmailDrafts(session, accountId, newEmail, oldEmailId); + yield Right( + UpdateEmailDraftsSuccess( + newEmailDrafts, + currentEmailState: currentEmailState, + currentMailboxState: currentMailboxState + ) + ); } catch (e) { yield Left(UpdateEmailDraftsFailure(e)); } diff --git a/lib/features/composer/presentation/base_composer_view.dart b/lib/features/composer/presentation/base_composer_view.dart index f5e0ab666..b405a7e77 100644 --- a/lib/features/composer/presentation/base_composer_view.dart +++ b/lib/features/composer/presentation/base_composer_view.dart @@ -513,7 +513,7 @@ abstract class BaseComposerView extends GetWidget width: 150, height: 44, radius: 10, - onTap: () => controller.saveToDraft(context) + onTap: () => controller.saveToDraftAction(context) ), const SizedBox(width: 12), buildTextButton( diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 81f454739..d083c5dd9 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -31,12 +31,15 @@ import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; import 'package:rich_text_composer/rich_text_composer.dart'; +import 'package:rxdart/rxdart.dart'; import 'package:super_tag_editor/tag_editor.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart'; import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'; import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/get_autocomplete_state.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/update_email_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; @@ -47,6 +50,7 @@ import 'package:tmail_ui_user/features/composer/presentation/controller/rich_tex import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/save_to_draft_view_event.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart'; @@ -147,11 +151,17 @@ class ComposerController extends BaseController { final ScrollController scrollControllerEmailAddress = ScrollController(); final ScrollController scrollControllerAttachment = ScrollController(); + final _saveToDraftEventController = StreamController(); + Stream get _saveToDraftEventStream => _saveToDraftEventController.stream; + late StreamSubscription _saveToDraftStreamSubscription; + List initialAttachments = []; String? _textEditorWeb; String? _initTextEditor; double? maxWithEditor; + EmailId? _emailIdEditing; late Worker uploadInlineImageWorker; + late Worker dashboardViewStateWorker; void onChangeTextEditorWeb(String? text) { initTextEditor(text); @@ -213,7 +223,7 @@ class ComposerController extends BaseController { super.onInit(); createFocusNodeInput(); scrollControllerEmailAddress.addListener(_scrollControllerEmailAddressListener); - _listenWorker(); + _listenStreamEvent(); if (PlatformInfo.isMobile) { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { await FkUserAgent.init(); @@ -258,11 +268,14 @@ class ComposerController extends BaseController { ccEmailAddressController.dispose(); bccEmailAddressController.dispose(); uploadInlineImageWorker.dispose(); + dashboardViewStateWorker.dispose(); keyboardRichTextController.dispose(); scrollController.dispose(); scrollControllerEmailAddress.removeListener(_scrollControllerEmailAddressListener); scrollControllerEmailAddress.dispose(); scrollControllerAttachment.dispose(); + _saveToDraftStreamSubscription.cancel(); + _saveToDraftEventController.close(); super.dispose(); } @@ -312,15 +325,31 @@ class ComposerController extends BaseController { } } - void _listenWorker() { + void _listenStreamEvent() { uploadInlineImageWorker = ever(uploadController.uploadInlineViewState, (state) { - log('ComposerController::_listenWorker(): $state'); + log('ComposerController::_listenStreamEvent()::uploadInlineImageWorker: $state'); state.fold((failure) => null, (success) { if (success is SuccessAttachmentUploadState) { _handleUploadInlineSuccess(success); } }); }); + + _saveToDraftStreamSubscription = _saveToDraftEventStream + .debounceTime(const Duration(milliseconds: 300)) + .listen(_handleSaveToDraft); + + dashboardViewStateWorker = ever(mailboxDashBoardController.viewState, (state) { + state.fold((failure) => null, (success) { + if (success is SaveEmailAsDraftsSuccess) { + _emailIdEditing = success.emailAsDrafts.id; + log('ComposerController::_listenStreamEvent::dashboardViewStateWorker:SaveEmailAsDraftsSuccess:emailIdEditing: $_emailIdEditing'); + } else if (success is UpdateEmailDraftsSuccess) { + _emailIdEditing = success.emailAsDrafts.id; + log('ComposerController::_listenStreamEvent::dashboardViewStateWorker:UpdateEmailDraftsSuccess:emailIdEditing: $_emailIdEditing'); + } + }); + }); } void _listenBrowserTabRefresh() { @@ -415,6 +444,7 @@ class ComposerController extends BaseController { emailId: arguments.presentationEmail!.id!, isDraftEmail: arguments.presentationEmail!.isDraft ); + _emailIdEditing = arguments.presentationEmail!.id!; break; case EmailActionType.editSendingEmail: _initEmailAddress( @@ -426,6 +456,7 @@ class ComposerController extends BaseController { actionType: EmailActionType.editSendingEmail ); _getEmailContentFromSendingEmail(arguments.sendingEmail!); + _emailIdEditing = arguments.sendingEmail!.presentationEmail.id!; break; case EmailActionType.composeFromContentShared: _getEmailContentFromContentShared(arguments.emailContents!); @@ -1027,7 +1058,8 @@ class ComposerController extends BaseController { if (arguments == null || userProfile == null || session == null || - accountId == null + accountId == null || + arguments.presentationEmail?.id != _emailIdEditing ) { if (PlatformInfo.isWeb) { mailboxDashBoardController.closeComposerOverlay(); @@ -1083,7 +1115,57 @@ class ComposerController extends BaseController { } } - void saveToDraft(BuildContext context) {} + void saveToDraftAction(BuildContext context) { + final userProfile = mailboxDashBoardController.userProfile.value; + final accountId = mailboxDashBoardController.accountId.value; + final session = mailboxDashBoardController.sessionCurrent; + final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts]; + + if (draftMailboxId == null || userProfile == null || session == null || accountId == null) { + logError('ComposerController::saveToDraftAction: Param is NULL'); + return; + } + + _saveToDraftEventController.add( + SaveToDraftViewEvent( + context: context, + session: session, + accountId: accountId, + userProfile: userProfile, + draftMailboxId: draftMailboxId, + emailIdEditing: _emailIdEditing, + ) + ); + } + + void _handleSaveToDraft(SaveToDraftViewEvent event) async { + log('ComposerController::_handleSaveToDraft:emailIdEditing: ${event.emailIdEditing}'); + final newEmail = await _generateEmail( + event.context, + event.userProfile, + asDrafts: true, + draftMailboxId: event.draftMailboxId + ); + + if (event.emailIdEditing == null) { + mailboxDashBoardController.consumeState( + _saveEmailAsDraftsInteractor.execute( + event.session, + event.accountId, + newEmail + ) + ); + } else { + mailboxDashBoardController.consumeState( + _updateEmailDraftsInteractor.execute( + event.session, + event.accountId, + newEmail, + event.emailIdEditing! + ) + ); + } + } File _covertSharedMediaFileToFile(SharedMediaFile sharedMediaFile) { return File( diff --git a/lib/features/composer/presentation/model/save_to_draft_view_event.dart b/lib/features/composer/presentation/model/save_to_draft_view_event.dart new file mode 100644 index 000000000..59c41de18 --- /dev/null +++ b/lib/features/composer/presentation/model/save_to_draft_view_event.dart @@ -0,0 +1,35 @@ +import 'package:core/presentation/state/success.dart'; +import 'package:flutter/widgets.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/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:model/user/user_profile.dart'; + +class SaveToDraftViewEvent extends ViewEvent { + final BuildContext context; + final Session session; + final AccountId accountId; + final UserProfile userProfile; + final MailboxId draftMailboxId; + final EmailId? emailIdEditing; + + SaveToDraftViewEvent({ + required this.context, + required this.session, + required this.accountId, + required this.userProfile, + required this.draftMailboxId, + this.emailIdEditing + }); + + @override + List get props => [ + context, + session, + accountId, + userProfile, + draftMailboxId, + emailIdEditing, + ]; +} \ 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 f6a86ca9c..e35f72c81 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -71,7 +71,7 @@ abstract class EmailDataSource { Future removeEmailDrafts(Session session, AccountId accountId, EmailId emailId); - Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail); + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId); Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds); 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 1490c7882..de40eaccc 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -113,9 +113,9 @@ class EmailDataSourceImpl extends EmailDataSource { } @override - Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail) { + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId) { return Future.sync(() async { - return await emailAPI.updateEmailDrafts(session, accountId, newEmail); + return await emailAPI.updateEmailDrafts(session, accountId, newEmail, oldEmailId); }).catchError(_exceptionThrower.throwException); } diff --git a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart index a94d77929..2b10b9b43 100644 --- a/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_hive_cache_datasource_impl.dart @@ -155,7 +155,7 @@ class EmailHiveCacheDataSourceImpl extends EmailDataSource { } @override - Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail) { + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId) { throw UnimplementedError(); } diff --git a/lib/features/email/data/network/email_api.dart b/lib/features/email/data/network/email_api.dart index 3d7fa89ac..23b92d26c 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -541,11 +541,13 @@ class EmailAPI with HandleSetErrorMixin { Future updateEmailDrafts( Session session, AccountId accountId, - Email newEmail + Email newEmail, + EmailId oldEmailId ) async { final idCreateMethod = Id(_uuid.v1()); final setEmailMethod = SetEmailMethod(accountId) - ..addCreate(idCreateMethod, newEmail); + ..addCreate(idCreateMethod, newEmail) + ..addDestroy({oldEmailId.id}); final requestBuilder = JmapRequestBuilder(_httpClient, ProcessingInvocation()); @@ -565,10 +567,11 @@ class EmailAPI with HandleSetErrorMixin { ); final emailUpdated = setEmailResponse?.created?[idCreateMethod]; + final isEmailDeleted = setEmailResponse?.destroyed?.contains(oldEmailId.id); final listEntriesErrors = _handleSetEmailResponse([setEmailResponse]); final mapErrors = Map.fromEntries(listEntriesErrors); - if (emailUpdated != null && mapErrors.isEmpty) { + if (emailUpdated != null && isEmailDeleted == true && mapErrors.isEmpty) { return emailUpdated; } else { throw SetEmailMethodException(mapErrors); diff --git a/lib/features/email/data/repository/email_repository_impl.dart b/lib/features/email/data/repository/email_repository_impl.dart index 554b7e486..a98c51b57 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -136,8 +136,8 @@ class EmailRepositoryImpl extends EmailRepository { } @override - Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail) { - return emailDataSource[DataSourceType.network]!.updateEmailDrafts(session, accountId, newEmail); + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId) { + return emailDataSource[DataSourceType.network]!.updateEmailDrafts(session, accountId, newEmail, oldEmailId); } @override diff --git a/lib/features/email/domain/exceptions/email_exceptions.dart b/lib/features/email/domain/exceptions/email_exceptions.dart index a59cbe218..3fd9b5d92 100644 --- a/lib/features/email/domain/exceptions/email_exceptions.dart +++ b/lib/features/email/domain/exceptions/email_exceptions.dart @@ -1,6 +1,3 @@ - -class CannotDeleteOldEmailException implements Exception {} - class NotFoundEmailException implements Exception {} class NotFoundEmailContentException implements Exception {} diff --git a/lib/features/email/domain/repository/email_repository.dart b/lib/features/email/domain/repository/email_repository.dart index 88f22aea4..9f319efb9 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -78,7 +78,7 @@ abstract class EmailRepository { Future removeEmailDrafts(Session session, AccountId accountId, EmailId emailId); - Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail); + Future updateEmailDrafts(Session session, AccountId accountId, Email newEmail, EmailId oldEmailId); Future> deleteMultipleEmailsPermanently(Session session, AccountId accountId, List emailIds);