From 904736a3aa0b47cc4e1107a84e73745782bc49c0 Mon Sep 17 00:00:00 2001 From: dab246 Date: Wed, 6 Mar 2024 03:02:18 +0700 Subject: [PATCH] TF-2667 Show saving message dialog when preform save action --- .../repository/composer_repository_impl.dart | 4 +- .../state/save_email_as_drafts_state.dart | 8 +- .../state/update_email_drafts_state.dart | 8 +- ...w_and_save_email_to_drafts_interactor.dart | 139 ++++++++++++ .../save_email_as_drafts_interactor.dart | 2 +- .../update_email_drafts_interactor.dart | 2 +- .../presentation/composer_bindings.dart | 9 +- .../presentation/composer_controller.dart | 202 +++++++++++------ .../widgets/saving_message_dialog_view.dart | 209 ++++++++++++++++++ .../widgets/sending_message_dialog_view.dart | 22 +- .../saving_message_to_drafts_dialog_view.dart | 208 +++++++++++++++++ .../mailbox_dashboard_controller.dart | 16 +- lib/l10n/intl_messages.arb | 26 ++- lib/main/localizations/app_localizations.dart | 24 ++ 14 files changed, 784 insertions(+), 95 deletions(-) create mode 100644 lib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart create mode 100644 lib/features/composer/presentation/widgets/saving_message_dialog_view.dart create mode 100644 lib/features/composer/presentation/widgets/web/saving_message_to_drafts_dialog_view.dart diff --git a/lib/features/composer/data/repository/composer_repository_impl.dart b/lib/features/composer/data/repository/composer_repository_impl.dart index 1ee0c4b91..2f502bd40 100644 --- a/lib/features/composer/data/repository/composer_repository_impl.dart +++ b/lib/features/composer/data/repository/composer_repository_impl.dart @@ -58,7 +58,9 @@ class ComposerRepositoryImpl extends ComposerRepository { emailAttachments.addAll(tupleContentInlineAttachments.value2); } - emailContent = await _removeCollapsedExpandedSignatureEffect(emailContent: emailContent); + if (createEmailRequest.draftsMailboxId == null) { + emailContent = await _removeCollapsedExpandedSignatureEffect(emailContent: emailContent); + } final userAgent = await _applicationManager.generateApplicationUserAgent(); final emailBodyPartId = PartId(_uuid.v1()); diff --git a/lib/features/composer/domain/state/save_email_as_drafts_state.dart b/lib/features/composer/domain/state/save_email_as_drafts_state.dart index e92f9d719..d2844f905 100644 --- a/lib/features/composer/domain/state/save_email_as_drafts_state.dart +++ b/lib/features/composer/domain/state/save_email_as_drafts_state.dart @@ -4,14 +4,14 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; -class SaveEmailAsDraftsLoading extends UIState {} +class SaveEmailAsDraftsLoading extends LoadingState {} class SaveEmailAsDraftsSuccess extends UIActionState { - final Email emailAsDrafts; + final EmailId emailId; SaveEmailAsDraftsSuccess( - this.emailAsDrafts, + this.emailId, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -19,7 +19,7 @@ class SaveEmailAsDraftsSuccess extends UIActionState { ) : super(currentEmailState, currentMailboxState); @override - List get props => [emailAsDrafts, ...super.props]; + List get props => [emailId, ...super.props]; } class SaveEmailAsDraftsFailure extends FeatureFailure { diff --git a/lib/features/composer/domain/state/update_email_drafts_state.dart b/lib/features/composer/domain/state/update_email_drafts_state.dart index af7ae6877..47ca09cca 100644 --- a/lib/features/composer/domain/state/update_email_drafts_state.dart +++ b/lib/features/composer/domain/state/update_email_drafts_state.dart @@ -4,14 +4,14 @@ import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; -class UpdatingEmailDrafts extends UIState {} +class UpdatingEmailDrafts extends LoadingState {} class UpdateEmailDraftsSuccess extends UIActionState { - final Email emailAsDrafts; + final EmailId emailId; UpdateEmailDraftsSuccess( - this.emailAsDrafts, + this.emailId, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -19,7 +19,7 @@ class UpdateEmailDraftsSuccess extends UIActionState { ) : super(currentEmailState, currentMailboxState); @override - List get props => [emailAsDrafts, ...super.props]; + List get props => [emailId, ...super.props]; } class UpdateEmailDraftsFailure extends FeatureFailure { diff --git a/lib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart b/lib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart new file mode 100644 index 000000000..c48920f72 --- /dev/null +++ b/lib/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart @@ -0,0 +1,139 @@ +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'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/generate_email_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/presentation/model/create_email_request.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'; + +class CreateNewAndSaveEmailToDraftsInteractor { + final EmailRepository _emailRepository; + final MailboxRepository _mailboxRepository; + final ComposerRepository _composerRepository; + + CreateNewAndSaveEmailToDraftsInteractor( + this._emailRepository, + this._mailboxRepository, + this._composerRepository, + ); + + Stream> execute(CreateEmailRequest createEmailRequest) async* { + try { + yield dartz.Right(GenerateEmailLoading()); + + final listCurrentState = await _getStoredCurrentState( + session: createEmailRequest.session, + accountId: createEmailRequest.accountId + ); + + final emailCreated = await _createEmailObject(createEmailRequest); + + if (emailCreated != null) { + if (createEmailRequest.draftsEmailId == null) { + yield dartz.Right(SaveEmailAsDraftsLoading()); + + final emailDraftSaved = await _emailRepository.saveEmailAsDrafts( + createEmailRequest.session, + createEmailRequest.accountId, + emailCreated + ); + + yield dartz.Right( + SaveEmailAsDraftsSuccess( + emailDraftSaved.id!, + currentMailboxState: listCurrentState?.value1, + currentEmailState: listCurrentState?.value2 + ) + ); + } else { + yield dartz.Right(UpdatingEmailDrafts()); + + final emailDraftSaved = await _emailRepository.updateEmailDrafts( + createEmailRequest.session, + createEmailRequest.accountId, + emailCreated, + createEmailRequest.draftsEmailId! + ); + + await _deleteOldDraftsEmail( + session: createEmailRequest.session, + accountId: createEmailRequest.accountId, + draftEmailId: createEmailRequest.draftsEmailId! + ); + + yield dartz.Right( + UpdateEmailDraftsSuccess( + emailDraftSaved.id!, + currentMailboxState: listCurrentState?.value1, + currentEmailState: listCurrentState?.value2 + ) + ); + } + } else { + yield dartz.Left(GenerateEmailFailure(CannotCreateEmailObjectException())); + } + } catch (e) { + logError('CreateNewAndSaveEmailToDraftsInteractor::execute: Exception: $e'); + if (createEmailRequest.draftsEmailId == null) { + yield dartz.Left(SaveEmailAsDraftsFailure(e)); + } else { + yield dartz.Left(UpdateEmailDraftsFailure(e)); + } + } + } + + Future _createEmailObject(CreateEmailRequest createEmailRequest) async { + try { + final emailCreated = await _composerRepository.generateEmail(createEmailRequest); + return emailCreated; + } catch (e) { + logError('CreateNewAndSaveEmailToDraftsInteractor::_createEmailObject: Exception: $e'); + return null; + } + } + + Future?> _getStoredCurrentState({ + required Session session, + required AccountId accountId + }) async { + try { + final listState = await Future.wait([ + _mailboxRepository.getMailboxState(session, accountId), + _emailRepository.getEmailState(session, accountId), + ]); + + final mailboxState = listState.first; + final emailState = listState.last; + + return dartz.Tuple2(mailboxState, emailState); + } catch (e) { + logError('CreateNewAndSaveEmailToDraftsInteractor::_getStoredCurrentState: Exception: $e'); + return null; + } + } + + Future _deleteOldDraftsEmail({ + required Session session, + required AccountId accountId, + required EmailId draftEmailId + }) async { + try { + await _emailRepository.removeEmailDrafts( + session, + accountId, + draftEmailId + ); + } catch (e) { + logError('CreateNewAndSaveEmailToDraftsInteractor::_deleteOldDraftsEmail: Exception: $e'); + } + } +} \ No newline at end of file diff --git a/lib/features/composer/domain/usecases/save_email_as_drafts_interactor.dart b/lib/features/composer/domain/usecases/save_email_as_drafts_interactor.dart index 511f3d181..58bbe60f3 100644 --- a/lib/features/composer/domain/usecases/save_email_as_drafts_interactor.dart +++ b/lib/features/composer/domain/usecases/save_email_as_drafts_interactor.dart @@ -28,7 +28,7 @@ class SaveEmailAsDraftsInteractor { final emailAsDrafts = await _emailRepository.saveEmailAsDrafts(session, accountId, email); yield Right( SaveEmailAsDraftsSuccess( - emailAsDrafts, + emailAsDrafts.id!, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState ) 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 77c74ae59..708066607 100644 --- a/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart +++ b/lib/features/composer/domain/usecases/update_email_drafts_interactor.dart @@ -29,7 +29,7 @@ class UpdateEmailDraftsInteractor { final newEmailDrafts = await _emailRepository.updateEmailDrafts(session, accountId, newEmail, oldEmailId); yield Right( UpdateEmailDraftsSuccess( - newEmailDrafts, + newEmailDrafts.id!, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState ) diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index 7f282169a..d22bf4cbf 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -1,6 +1,5 @@ import 'package:core/core.dart'; import 'package:core/utils/application_manager.dart'; -import 'package:core/utils/file_utils.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/base/base_bindings.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; @@ -11,6 +10,7 @@ import 'package:tmail_ui_user/features/composer/data/repository/composer_reposit import 'package:tmail_ui_user/features/composer/data/repository/contact_repository_impl.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.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/save_email_as_drafts_interactor.dart'; @@ -208,6 +208,11 @@ class ComposerBindings extends BaseBindings { Get.find(), Get.find(), )); + Get.lazyPut(() => CreateNewAndSaveEmailToDraftsInteractor( + Get.find(), + Get.find(), + Get.find(), + )); IdentityInteractorsBindings().dependencies(); } @@ -229,6 +234,8 @@ class ComposerBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), + Get.find(), )); } diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 40f39a6bb..730db176d 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -43,6 +43,7 @@ import 'package:tmail_ui_user/features/composer/domain/state/get_device_contact_ 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/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.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_all_autocomplete_interactor.dart'; @@ -63,6 +64,7 @@ import 'package:tmail_ui_user/features/composer/presentation/model/save_to_draft import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/saving_message_dialog_view.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/sending_message_dialog_view.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'; @@ -133,6 +135,7 @@ class ComposerController extends BaseController with DragDropFileMixin { final TransformHtmlEmailContentInteractor _transformHtmlEmailContentInteractor; final GetAlwaysReadReceiptSettingInteractor _getAlwaysReadReceiptSettingInteractor; final CreateNewAndSendEmailInteractor _createNewAndSendEmailInteractor; + final CreateNewAndSaveEmailToDraftsInteractor _createNewAndSaveEmailToDraftsInteractor; GetAllAutoCompleteInteractor? _getAllAutoCompleteInteractor; GetAutoCompleteInteractor? _getAutoCompleteInteractor; @@ -202,6 +205,7 @@ class ComposerController extends BaseController with DragDropFileMixin { this._transformHtmlEmailContentInteractor, this._getAlwaysReadReceiptSettingInteractor, this._createNewAndSendEmailInteractor, + this._createNewAndSaveEmailToDraftsInteractor, ); @override @@ -348,11 +352,11 @@ class ComposerController extends BaseController with DragDropFileMixin { }, (success) { if (success is SaveEmailAsDraftsSuccess) { - _emailIdEditing = success.emailAsDrafts.id; + _emailIdEditing = success.emailId; _saveToDraftButtonState = ButtonState.enabled; log('ComposerController::_listenStreamEvent::dashboardViewStateWorker:SaveEmailAsDraftsSuccess:emailIdEditing: $_emailIdEditing'); } else if (success is UpdateEmailDraftsSuccess) { - _emailIdEditing = success.emailAsDrafts.id; + _emailIdEditing = success.emailId; _saveToDraftButtonState = ButtonState.enabled; log('ComposerController::_listenStreamEvent::dashboardViewStateWorker:UpdateEmailDraftsSuccess:emailIdEditing: $_emailIdEditing'); } @@ -1211,65 +1215,6 @@ class ComposerController extends BaseController with DragDropFileMixin { return false; } - Future _generateSaveAsDraftsArguments(BuildContext context) async { - log('ComposerController::_generateSaveAsDraftsArguments:'); - final arguments = composerArguments.value; - final accountId = mailboxDashBoardController.accountId.value; - final session = mailboxDashBoardController.sessionCurrent; - final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts]; - - if (arguments == null || - draftMailboxId == null || - session == null || - accountId == null - ) { - return null; - } - - if (_emailIdEditing != null && _emailIdEditing != arguments.presentationEmail?.id) { - final newEmail = await _generateEmail( - context, - session.username, - asDrafts: true, - draftMailboxId: draftMailboxId, - arguments: arguments, - ); - - return SaveToDraftArguments( - session: session, - accountId: accountId, - newEmail: newEmail, - oldEmailId: _emailIdEditing!); - } else { - final isChanged = await _validateEmailChange( - context: context, - emailActionType: arguments.emailActionType, - presentationEmail: arguments.presentationEmail, - mailboxRole: arguments.mailboxRole - ); - - if (isChanged && context.mounted) { - final newEmail = await _generateEmail( - context, - session.username, - asDrafts: true, - draftMailboxId: draftMailboxId, - arguments: arguments, - ); - - return SaveToDraftArguments( - session: session, - accountId: accountId, - newEmail: newEmail, - oldEmailId: arguments.emailActionType == EmailActionType.editDraft - ? arguments.presentationEmail?.id - : null); - } else { - return null; - } - } - } - void saveToDraftAction(BuildContext context) async { if (_saveToDraftButtonState == ButtonState.disabled) { log('ComposerController::saveToDraftAction: Saving to draft'); @@ -2103,9 +2048,10 @@ class ComposerController extends BaseController with DragDropFileMixin { AppLocalizations.of(context).save, cancelTitle: AppLocalizations.of(context).discardChanges, alignCenter: true, - onConfirmAction: () { - _closeComposerButtonState = ButtonState.enabled; - }, + onConfirmAction: () async => await Future.delayed( + const Duration(milliseconds: 100), + () => _handleSaveMessageToDraft(context) + ), onCancelAction: () async { _closeComposerButtonState = ButtonState.enabled; await Future.delayed( @@ -2180,4 +2126,132 @@ class ComposerController extends BaseController with DragDropFileMixin { onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo) ); } + + void _handleSaveMessageToDraft(BuildContext context) async { + if (composerArguments.value == null || + mailboxDashBoardController.sessionCurrent == null || + mailboxDashBoardController.accountId.value == null || + mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts] == null + ) { + log('ComposerController::_handleSaveMessageToDraft: SESSION or ACCOUNT_ID or ARGUMENTS is NULL'); + _closeComposerButtonState = ButtonState.enabled; + _closeComposerAction(); + return; + } + + final emailContent = await _getContentInEditor(); + final draftEmailId = _getDraftEmailId(); + log('ComposerController::_handleSaveMessageToDraft: draftEmailId = $draftEmailId'); + final resultState = await _showSavingMessageToDraftsDialog( + emailContent: emailContent, + draftEmailId: draftEmailId + ); + + if (resultState is SaveEmailAsDraftsSuccess || resultState is UpdateEmailDraftsSuccess) { + _closeComposerButtonState = ButtonState.enabled; + _closeComposerAction(result: resultState); + } else if ((resultState is SaveEmailAsDraftsFailure || + resultState is UpdateEmailDraftsFailure || + resultState is GenerateEmailFailure) && + context.mounted + ) { + _showConfirmDialogWhenSaveMessageToDraftsFailure( + context: context, + failure: resultState + ); + } else { + _closeComposerButtonState = ButtonState.enabled; + } + } + + EmailId? _getDraftEmailId() { + if (_emailIdEditing != null && + _emailIdEditing != composerArguments.value!.presentationEmail?.id) { + return _emailIdEditing; + } else if (composerArguments.value!.emailActionType == EmailActionType.editDraft) { + return composerArguments.value!.presentationEmail?.id; + } else { + return null; + } + } + + Future _showSavingMessageToDraftsDialog({ + required String emailContent, + EmailId? draftEmailId, + }) { + return Get.dialog( + PointerInterceptor( + child: SavingMessageDialogView( + createEmailRequest: CreateEmailRequest( + session: mailboxDashBoardController.sessionCurrent!, + accountId: mailboxDashBoardController.accountId.value!, + emailActionType: composerArguments.value!.emailActionType, + subject: subjectEmail.value ?? '', + emailContent: emailContent, + fromSender: composerArguments.value!.presentationEmail?.from ?? {}, + toRecipients: listToEmailAddress.toSet(), + ccRecipients: listCcEmailAddress.toSet(), + bccRecipients: listBccEmailAddress.toSet(), + isRequestReadReceipt: hasRequestReadReceipt.value, + identity: identitySelected.value, + attachments: uploadController.attachmentsUploaded, + inlineAttachments: uploadController.mapInlineAttachments, + sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent], + draftsMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts], + draftsEmailId: draftEmailId, + answerForwardEmailId: composerArguments.value!.presentationEmail?.id, + unsubscribeEmailId: composerArguments.value!.previousEmailId, + messageId: composerArguments.value!.messageId, + references: composerArguments.value!.references, + emailSendingQueue: composerArguments.value!.sendingEmail + ), + createNewAndSaveEmailToDraftsInteractor: _createNewAndSaveEmailToDraftsInteractor + ), + ), + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + ); + } + + void _showConfirmDialogWhenSaveMessageToDraftsFailure({ + required BuildContext context, + required FeatureFailure failure + }) { + showConfirmDialogAction( + context, + title: '', + AppLocalizations.of(context).warningMessageWhenSaveEmailToDraftsFailure, + AppLocalizations.of(context).edit, + cancelTitle: AppLocalizations.of(context).closeAnyway, + alignCenter: true, + onConfirmAction: () { + _closeComposerButtonState = ButtonState.enabled; + _autoFocusFieldWhenLauncher(); + }, + onCancelAction: () async { + _closeComposerButtonState = ButtonState.enabled; + await Future.delayed( + const Duration(milliseconds: 100), + _closeComposerAction + ); + }, + icon: SvgPicture.asset( + imagePaths.icQuotasWarning, + width: 40, + height: 40, + colorFilter: AppColor.colorBackgroundQuotasWarning.asFilter(), + ), + messageStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 14, + color: AppColor.colorTextBody + ), + actionStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 17, + color: Colors.white + ), + cancelStyle: Theme.of(context).textTheme.labelMedium?.copyWith( + fontSize: 17, + color: Colors.black + ) + ); + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/widgets/saving_message_dialog_view.dart b/lib/features/composer/presentation/widgets/saving_message_dialog_view.dart new file mode 100644 index 000000000..63e9962c9 --- /dev/null +++ b/lib/features/composer/presentation/widgets/saving_message_dialog_view.dart @@ -0,0 +1,209 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:core/presentation/extensions/capitalize_extension.dart'; +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'; +import 'package:dartz/dartz.dart' as dartz; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/generate_email_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/create_new_and_save_email_to_drafts_interactor.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class SavingMessageDialogView extends StatefulWidget { + + final CreateEmailRequest createEmailRequest; + final CreateNewAndSaveEmailToDraftsInteractor createNewAndSaveEmailToDraftsInteractor; + + const SavingMessageDialogView({ + super.key, + required this.createEmailRequest, + required this.createNewAndSaveEmailToDraftsInteractor, + }); + + @override + State createState() => _SavingMessageDialogViewState(); +} + +class _SavingMessageDialogViewState extends State { + + StreamSubscription? _streamSubscription; + final ValueNotifier?> _viewStateNotifier = ValueNotifier(null); + + @override + void initState() { + super.initState(); + _streamSubscription = widget.createNewAndSaveEmailToDraftsInteractor + .execute(widget.createEmailRequest) + .listen( + _handleDataStream, + onError: _handleErrorStream + ); + } + + void _handleDataStream(dartz.Either newState) { + _viewStateNotifier.value = newState; + + newState.fold( + (failure) { + if (failure is SaveEmailAsDraftsFailure || + failure is UpdateEmailDraftsFailure || + failure is GenerateEmailFailure) { + popBack(result: failure); + } + }, + (success) { + if (success is SaveEmailAsDraftsSuccess || success is UpdateEmailDraftsSuccess) { + popBack(result: success); + } + } + ); + } + + void _handleErrorStream(Object error, StackTrace stackTrace) { + logError('_SavingMessageDialogViewState::_handleErrorStream: Exception = $error'); + popBack(result: SaveEmailAsDraftsFailure(error)); + } + + @override + Widget build(BuildContext context) { + return Dialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + insetPadding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 16.0 + ), + alignment: Alignment.center, + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: Colors.white, + ), + width: min(context.width, 400), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + padding: const EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 12), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: AppColor.colorItemSelected, + ), + alignment: Alignment.center, + child: Text( + AppLocalizations.of(context).savingMessage.capitalizeFirstEach, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 17 + ), + ), + ), + const Divider(), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 12, bottom: 4), + child: Row( + children: [ + Text( + '${AppLocalizations.of(context).status}:', + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 14 + ), + ), + const SizedBox(width: 8), + Expanded( + child: ValueListenableBuilder( + valueListenable: _viewStateNotifier, + builder: (context, value, child) { + if (value == null) { + return child!; + } + + return value.fold( + (failure) => child!, + (success) { + if (success is GenerateEmailLoading) { + return Text( + '${AppLocalizations.of(context).creatingMessage}...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ); + } else { + return Text( + '${AppLocalizations.of(context).savingMessageToDraftFolder}...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ); + } + } + ); + }, + child: Text( + '...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ), + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 4, bottom: 24), + child: Row( + children: [ + Text( + '${AppLocalizations.of(context).progress}:', + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 14 + ), + ), + const SizedBox(width: 8), + Expanded( + child: LinearProgressIndicator( + color: Colors.white.withOpacity(0.6), + backgroundColor: AppColor.primaryColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + ) + ], + ), + ) + ], + ) + ], + ), + ), + ); + } + + @override + void dispose() { + _streamSubscription?.cancel(); + _viewStateNotifier.dispose(); + super.dispose(); + } +} diff --git a/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart b/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart index f80d9b574..d59cc9f33 100644 --- a/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart +++ b/lib/features/composer/presentation/widgets/sending_message_dialog_view.dart @@ -33,13 +33,13 @@ class SendingMessageDialogView extends StatefulWidget { class _SendingMessageDialogViewState extends State { - StreamSubscription? _sendingStreamSubscription; - final ValueNotifier?> _sendingNotifier = ValueNotifier(null); + StreamSubscription? _streamSubscription; + final ValueNotifier?> _viewStateNotifier = ValueNotifier(null); @override void initState() { super.initState(); - _sendingStreamSubscription = widget.createNewAndSendEmailInteractor + _streamSubscription = widget.createNewAndSendEmailInteractor .execute(widget.createEmailRequest) .listen( _handleDataStream, @@ -48,7 +48,7 @@ class _SendingMessageDialogViewState extends State { } void _handleDataStream(dartz.Either newState) { - _sendingNotifier.value = newState; + _viewStateNotifier.value = newState; newState.fold( (failure) { @@ -125,7 +125,7 @@ class _SendingMessageDialogViewState extends State { const SizedBox(width: 8), Expanded( child: ValueListenableBuilder( - valueListenable: _sendingNotifier, + valueListenable: _viewStateNotifier, builder: (context, value, child) { if (value == null) { return child!; @@ -136,13 +136,13 @@ class _SendingMessageDialogViewState extends State { (success) { if (success is GenerateEmailLoading) { return Text( - 'Creating email...', + '${AppLocalizations.of(context).creatingMessage}...', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppColor.labelColor, fontSize: 14 ), ); - } else if (success is SendEmailLoading) { + } else { return Text( '${AppLocalizations.of(context).sendingMessage}...', style: Theme.of(context).textTheme.labelSmall?.copyWith( @@ -150,14 +150,12 @@ class _SendingMessageDialogViewState extends State { fontSize: 14 ), ); - } else { - return child!; } } ); }, child: Text( - '${AppLocalizations.of(context).sendingMessage}...', + '...', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppColor.labelColor, fontSize: 14 @@ -201,8 +199,8 @@ class _SendingMessageDialogViewState extends State { @override void dispose() { - _sendingStreamSubscription?.cancel(); - _sendingNotifier.dispose(); + _streamSubscription?.cancel(); + _viewStateNotifier.dispose(); super.dispose(); } } diff --git a/lib/features/composer/presentation/widgets/web/saving_message_to_drafts_dialog_view.dart b/lib/features/composer/presentation/widgets/web/saving_message_to_drafts_dialog_view.dart new file mode 100644 index 000000000..ea8db6d62 --- /dev/null +++ b/lib/features/composer/presentation/widgets/web/saving_message_to_drafts_dialog_view.dart @@ -0,0 +1,208 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:core/presentation/extensions/capitalize_extension.dart'; +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'; +import 'package:dartz/dartz.dart' as dartz; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +class SavingMessageToDraftsDialogView extends StatefulWidget { + + final CreateEmailRequest createEmailRequest; + final CreateNewAndSendEmailInteractor createNewAndSendEmailInteractor; + + const SavingMessageToDraftsDialogView({ + super.key, + required this.createEmailRequest, + required this.createNewAndSendEmailInteractor, + }); + + @override + State createState() => _SavingMessageToDraftsDialogViewState(); +} + +class _SavingMessageToDraftsDialogViewState extends State { + + StreamSubscription? _sendingStreamSubscription; + final ValueNotifier?> _sendingNotifier = ValueNotifier(null); + + @override + void initState() { + super.initState(); + _sendingStreamSubscription = widget.createNewAndSendEmailInteractor + .execute(widget.createEmailRequest) + .listen( + _handleDataStream, + onError: _handleErrorStream + ); + } + + void _handleDataStream(dartz.Either newState) { + _sendingNotifier.value = newState; + + newState.fold( + (failure) { + if (failure is SendEmailFailure || failure is GenerateEmailFailure) { + popBack(result: failure); + } + }, + (success) { + if (success is SendEmailSuccess) { + popBack(result: success); + } + } + ); + } + + void _handleErrorStream(Object error, StackTrace stackTrace) { + logError('_SendingMessageDialogViewState::_handleErrorStream: Exception = $error'); + popBack(result: SendEmailFailure(exception: error)); + } + + @override + Widget build(BuildContext context) { + return Dialog( + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(12))), + insetPadding: const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 16.0 + ), + alignment: Alignment.center, + child: Container( + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: Colors.white, + ), + width: min(context.width, 400), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: double.infinity, + clipBehavior: Clip.antiAlias, + padding: const EdgeInsetsDirectional.symmetric(vertical: 8, horizontal: 12), + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(12)), + color: AppColor.colorItemSelected, + ), + alignment: Alignment.center, + child: Text( + AppLocalizations.of(context).sendingMessage.capitalizeFirstEach, + style: Theme.of(context).textTheme.labelLarge?.copyWith( + color: Colors.black, + fontWeight: FontWeight.bold, + fontSize: 17 + ), + ), + ), + const Divider(), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 12, bottom: 4), + child: Row( + children: [ + Text( + '${AppLocalizations.of(context).status}:', + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 14 + ), + ), + const SizedBox(width: 8), + Expanded( + child: ValueListenableBuilder( + valueListenable: _sendingNotifier, + builder: (context, value, child) { + if (value == null) { + return child!; + } + + return value.fold( + (failure) => child!, + (success) { + if (success is GenerateEmailLoading) { + return Text( + 'Creating email...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ); + } else if (success is SendEmailLoading) { + return Text( + '${AppLocalizations.of(context).sendingMessage}...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ); + } else { + return child!; + } + } + ); + }, + child: Text( + '${AppLocalizations.of(context).sendingMessage}...', + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: AppColor.labelColor, + fontSize: 14 + ), + ), + ), + ) + ], + ), + ), + Padding( + padding: const EdgeInsetsDirectional.only(start: 16, end: 16, top: 4, bottom: 24), + child: Row( + children: [ + Text( + '${AppLocalizations.of(context).progress}:', + style: Theme.of(context).textTheme.labelMedium?.copyWith( + color: Colors.black, + fontWeight: FontWeight.w500, + fontSize: 14 + ), + ), + const SizedBox(width: 8), + Expanded( + child: LinearProgressIndicator( + color: Colors.white.withOpacity(0.6), + backgroundColor: AppColor.primaryColor, + borderRadius: const BorderRadius.all(Radius.circular(12)), + ), + ) + ], + ), + ) + ], + ) + ], + ), + ), + ); + } + + @override + void dispose() { + _sendingStreamSubscription?.cancel(); + _sendingNotifier.dispose(); + super.dispose(); + } +} 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 42e14e4a0..76b385f63 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -656,7 +656,7 @@ class MailboxDashBoardController extends ReloadableController { currentOverlayContext!, AppLocalizations.of(currentContext!).drafts_saved, actionName: AppLocalizations.of(currentContext!).discard, - onActionClick: () => _discardEmail(success.emailAsDrafts), + onActionClick: () => _discardEmail(success.emailId), leadingSVGIcon: imagePaths.icMailboxDrafts, leadingSVGIconColor: Colors.white, backgroundColor: AppColor.toastSuccessBackgroundColor, @@ -699,11 +699,11 @@ class MailboxDashBoardController extends ReloadableController { } } - void _discardEmail(Email email) { + void _discardEmail(EmailId emailId) { final currentAccountId = accountId.value; final session = sessionCurrent; - if (currentAccountId != null && session != null && email.id != null) { - consumeState(_removeEmailDraftsInteractor.execute(session, currentAccountId, email.id!)); + if (currentAccountId != null && session != null) { + consumeState(_removeEmailDraftsInteractor.execute(session, currentAccountId, emailId)); } } @@ -1260,7 +1260,9 @@ class MailboxDashBoardController extends ReloadableController { handleSendEmailAction(result); } else if (result is SaveToDraftArguments) { saveEmailToDraft(arguments: result); - } else if (result is SendEmailSuccess) { + } else if (result is SendEmailSuccess || + result is SaveEmailAsDraftsSuccess || + result is UpdateEmailDraftsSuccess) { consumeState(Stream.value(Right(result))); } } @@ -1395,7 +1397,9 @@ class MailboxDashBoardController extends ReloadableController { handleSendEmailAction(result); } else if (result is SaveToDraftArguments) { saveEmailToDraft(arguments: result); - } else if (result is SendEmailSuccess) { + } else if (result is SendEmailSuccess || + result is SaveEmailAsDraftsSuccess || + result is UpdateEmailDraftsSuccess) { consumeState(Stream.value(Right(result))); } } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index dbbfcce02..6d2997da9 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2024-03-06T01:50:29.250412", + "@@last_modified": "2024-03-06T02:46:19.947772", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3821,5 +3821,29 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "savingMessage": "Saving message", + "@savingMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "creatingMessage": "Creating message", + "@creatingMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "savingMessageToDraftFolder": "Saving message to draft folder", + "@savingMessageToDraftFolder": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "warningMessageWhenSaveEmailToDraftsFailure": "Saving of the message to drafts folder failed.\nAn error occurred while saving mail.", + "@warningMessageWhenSaveEmailToDraftsFailure": { + "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 0216e2501..222f04fdb 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3992,4 +3992,28 @@ class AppLocalizations { 'Save this message to your drafts folder and close composer?', name: 'warningMessageWhenClickCloseComposer'); } + + String get savingMessage { + return Intl.message( + 'Saving message', + name: 'savingMessage'); + } + + String get creatingMessage { + return Intl.message( + 'Creating message', + name: 'creatingMessage'); + } + + String get savingMessageToDraftFolder { + return Intl.message( + 'Saving message to draft folder', + name: 'savingMessageToDraftFolder'); + } + + String get warningMessageWhenSaveEmailToDraftsFailure { + return Intl.message( + 'Saving of the message to drafts folder failed.\nAn error occurred while saving mail.', + name: 'warningMessageWhenSaveEmailToDraftsFailure'); + } } \ No newline at end of file