diff --git a/lib/features/base/base_bindings.dart b/lib/features/base/base_bindings.dart new file mode 100644 index 000000000..164cdc674 --- /dev/null +++ b/lib/features/base/base_bindings.dart @@ -0,0 +1,27 @@ + +import 'package:get/get_instance/src/bindings_interface.dart'; + +abstract class BaseBindings extends Bindings { + + @override + void dependencies() { + bindingsDataSourceImpl(); + bindingsDataSource(); + bindingsRepositoryImpl(); + bindingsRepository(); + bindingsInteractor(); + bindingsController(); + } + + void bindingsDataSourceImpl(); + + void bindingsDataSource(); + + void bindingsRepositoryImpl(); + + void bindingsRepository(); + + void bindingsInteractor(); + + void bindingsController(); +} \ No newline at end of file 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 new file mode 100644 index 000000000..2035027ba --- /dev/null +++ b/lib/features/composer/domain/state/save_email_as_drafts_state.dart @@ -0,0 +1,21 @@ +import 'package:core/core.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; + +class SaveEmailAsDraftsSuccess extends UIState { + + final Email emailAsDrafts; + + SaveEmailAsDraftsSuccess(this.emailAsDrafts); + + @override + List get props => []; +} + +class SaveEmailAsDraftsFailure extends FeatureFailure { + final exception; + + SaveEmailAsDraftsFailure(this.exception); + + @override + List get props => [exception]; +} \ 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 new file mode 100644 index 000000000..cddb85c59 --- /dev/null +++ b/lib/features/composer/domain/usecases/save_email_as_drafts_interactor.dart @@ -0,0 +1,25 @@ +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/composer/domain/state/save_email_as_drafts_state.dart'; +import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; + +class SaveEmailAsDraftsInteractor { + final EmailRepository emailRepository; + + SaveEmailAsDraftsInteractor(this.emailRepository); + + Stream> execute(AccountId accountId, Email email) async* { + try { + final result = await emailRepository.saveEmailAsDrafts(accountId, email); + if (result) { + yield Right(SaveEmailAsDraftsSuccess(email)); + } else { + yield Left(SaveEmailAsDraftsFailure(result)); + } + } catch (e) { + yield Left(SaveEmailAsDraftsFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart index bd872bf03..12ab6c938 100644 --- a/lib/features/composer/presentation/composer_bindings.dart +++ b/lib/features/composer/presentation/composer_bindings.dart @@ -2,6 +2,7 @@ import 'package:core/core.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/cupertino.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/autocomplete_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart'; import 'package:tmail_ui_user/features/composer/data/datasource/contact_datasource.dart'; @@ -20,62 +21,117 @@ import 'package:tmail_ui_user/features/composer/domain/repository/composer_repos import 'package:tmail_ui_user/features/composer/domain/repository/contact_repository.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_as_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_addresses_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/upload_mutiple_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/datasource_impl/html_datasource_impl.dart'; +import 'package:tmail_ui_user/features/email/data/local/html_analyzer.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/upload/domain/usecases/local_file_picker_interactor.dart'; import 'package:uuid/uuid.dart'; -class ComposerBindings extends Bindings { +class ComposerBindings extends BaseBindings { @override void dependencies() { + _bindingsUtils(); + super.dependencies(); + } + + void _bindingsUtils() { Get.lazyPut(() => EmailAddressDatabaseManager(Get.find())); + Get.lazyPut(() => Uuid()); + } + + @override + void bindingsDataSourceImpl() { Get.lazyPut(() => ComposerDataSourceImpl(Get.find())); Get.lazyPut(() => LocalComposerDataSourceImpl(Get.find())); + Get.lazyPut(() => AutoCompleteDataSourceImpl()); + Get.lazyPut(() => LocalAutoCompleteDataSourceImpl(Get.find())); + Get.lazyPut(() => ContactDataSourceImpl()); + Get.lazyPut(() => HtmlDataSourceImpl( + Get.find(), + Get.find() + )); + } + + @override + void bindingsDataSource() { Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + } + + @override + void bindingsRepositoryImpl() { Get.lazyPut(() => ComposerRepositoryImpl({ DataSourceType.network: Get.find(), DataSourceType.local: Get.find(), })); - Get.lazyPut(() => Get.find()); - Get.lazyPut(() => SaveEmailAddressesInteractor(Get.find())); - Get.lazyPut(() => AutoCompleteDataSourceImpl()); - Get.lazyPut(() => Get.find()); - Get.lazyPut(() => LocalAutoCompleteDataSourceImpl(Get.find())); Get.lazyPut(() => AutoCompleteRepositoryImpl({ DataSourceType.network: Get.find(), DataSourceType.local: Get.find(), })); - Get.lazyPut(() => Get.find()); - Get.lazyPut(() => GetAutoCompleteInteractor(Get.find())); - Get.lazyPut(() => Uuid()); - Get.lazyPut(() => TextEditingController()); - Get.lazyPut(() => ContactDataSourceImpl()); - Get.lazyPut(() => Get.find()); Get.lazyPut(() => ContactRepositoryImpl(Get.find())); + Get.lazyPut(() => EmailDataSourceImpl(Get.find())); + Get.lazyPut(() => EmailRepositoryImpl( + Get.find(), + Get.find() + )); + } + + @override + void bindingsRepository() { + Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); + Get.lazyPut(() => Get.find()); + + } + + @override + void bindingsInteractor() { + Get.lazyPut(() => SaveEmailAddressesInteractor(Get.find())); + Get.lazyPut(() => GetAutoCompleteInteractor(Get.find())); Get.lazyPut(() => GetDeviceContactSuggestionsInteractor(Get.find())); Get.lazyPut(() => GetAutoCompleteWithDeviceContactInteractor( Get.find(), - Get.find())); + Get.find() + )); Get.lazyPut(() => LocalFilePickerInteractor()); - Get.lazyPut(() => UploadAttachmentInteractor( - Get.find())); + Get.lazyPut(() => UploadAttachmentInteractor(Get.find())); Get.lazyPut(() => UploadMultipleAttachmentInteractor(Get.find())); + Get.lazyPut(() => SendEmailInteractor(Get.find())); + Get.lazyPut(() => SaveEmailAsDraftsInteractor(Get.find())); + } + + @override + void bindingsController() { + Get.lazyPut(() => TextEditingController()); Get.lazyPut(() => ComposerController( - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find(), - Get.find())); + Get.find(), + Get.find(), + Get.find(), + Get.find(), + 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/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 0d58427b6..67ef2e0bf 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -19,6 +19,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_body_value.dart'; import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart'; +import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/model.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; @@ -30,6 +31,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_with_device_contact_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/state/upload_attachment_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_addresses_interactor.dart'; +import 'package:tmail_ui_user/features/composer/domain/usecases/save_email_as_drafts_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/upload_mutiple_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; @@ -62,6 +64,7 @@ class ComposerController extends BaseController { final LocalFilePickerInteractor _localFilePickerInteractor; final UploadMultipleAttachmentInteractor _uploadMultipleAttachmentInteractor; final DeviceInfoPlugin _deviceInfoPlugin; + final SaveEmailAsDraftsInteractor _saveEmailAsDraftsInteractor; List listToEmailAddress = []; List listCcEmailAddress = []; @@ -96,6 +99,7 @@ class ComposerController extends BaseController { this.subjectEmailInputController, this._localFilePickerInteractor, this._uploadMultipleAttachmentInteractor, + this._saveEmailAsDraftsInteractor, ); @override @@ -272,9 +276,10 @@ class ComposerController extends BaseController { return emailQuotedHtml; } - Future generateEmail() async { + Future generateEmail({bool asDrafts = false}) async { final generateEmailId = EmailId(Id(_uuid.v1())); final outboxMailboxId = composerArguments.value!.mapMailboxId[PresentationMailbox.roleOutbox]; + final draftMailboxId = composerArguments.value!.mapMailboxId[PresentationMailbox.roleDrafts]; final listFromEmailAddress = { EmailAddress(null, composerArguments.value!.userProfile.email) }; @@ -286,11 +291,12 @@ class ComposerController extends BaseController { return Email( generateEmailId, - mailboxIds: {outboxMailboxId!: true}, + mailboxIds: asDrafts ? {draftMailboxId!: true} : {outboxMailboxId!: true}, from: listFromEmailAddress, to: listToEmailAddress.toSet(), cc: listCcEmailAddress.toSet(), bcc: listBccEmailAddress.toSet(), + keywords: asDrafts ? {KeyWordIdentifier.emailDraft : true} : null, subject: _subjectEmail, htmlBody: { EmailBodyPart( @@ -450,6 +456,15 @@ class ComposerController extends BaseController { attachments.removeWhere((attachment) => attachment == attachmentRemoved); } + void saveEmailAsDrafts() async { + _saveEmailAddress(); + + final newEmail = await generateEmail(asDrafts: true); + final accountId = composerArguments.value!.session.accounts.keys.first; + + mailboxDashBoardController.consumeState(_saveEmailAsDraftsInteractor.execute(accountId, newEmail)); + } + void backToEmailViewAction() { popBack(); } diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 889030ce3..9dcac9957 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -22,28 +22,35 @@ class ComposerView extends GetWidget { @override Widget build(BuildContext context) { - return GestureDetector( - onTap: () { + return WillPopScope( + onWillPop: () async { controller.htmlEditorApi?.unfocus(context); + controller.saveEmailAsDrafts(); + return true; }, - child: Scaffold( - backgroundColor: AppColor.primaryLightColor, - body: SafeArea( - right: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), - left: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), - child: Container( - margin: EdgeInsets.zero, - alignment: Alignment.topCenter, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.max, - children: [ - _buildTopBar(context), - Expanded(child: _buildBodyComposer(context)) - ]) - ) - ), + child: GestureDetector( + onTap: () { + controller.htmlEditorApi?.unfocus(context); + }, + child: Scaffold( + backgroundColor: AppColor.primaryLightColor, + body: SafeArea( + right: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), + left: responsiveUtils.isMobileDevice(context) && responsiveUtils.isLandscape(context), + child: Container( + margin: EdgeInsets.zero, + alignment: Alignment.topCenter, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + _buildTopBar(context), + Expanded(child: _buildBodyComposer(context)) + ]) + ) + ), + ) ) ); } @@ -54,7 +61,10 @@ class ComposerView extends GetWidget { child: Obx(() => (TopBarComposerWidgetBuilder(imagePaths, controller.isEnableEmailSendButton.value) ..addSendEmailActionClick(() => controller.sendEmailAction(context)) ..addAttachFileActionClick(() => controller.openPickAttachmentMenu(context, _pickAttachmentsActionTiles(context))) - ..addBackActionClick(() => controller.backToEmailViewAction())) + ..addBackActionClick(() { + controller.saveEmailAsDrafts(); + controller.backToEmailViewAction(); + })) .build()), ); } diff --git a/lib/features/email/data/datasource/email_datasource.dart b/lib/features/email/data/datasource/email_datasource.dart index 5d1e9a619..0c6ea0e5c 100644 --- a/lib/features/email/data/datasource/email_datasource.dart +++ b/lib/features/email/data/datasource/email_datasource.dart @@ -34,4 +34,6 @@ abstract class EmailDataSource { List emails, MarkStarAction markStarAction ); + + Future saveEmailAsDrafts(AccountId accountId, Email email); } \ 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 2d119df83..41847d7a0 100644 --- a/lib/features/email/data/datasource_impl/email_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/email_datasource_impl.dart @@ -86,4 +86,13 @@ class EmailDataSourceImpl extends EmailDataSource { throw error; }); } + + @override + Future saveEmailAsDrafts(AccountId accountId, Email email) { + return Future.sync(() async { + return await emailAPI.saveEmailAsDrafts(accountId, email); + }).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 d9d45fa67..1ea5f58b1 100644 --- a/lib/features/email/data/network/email_api.dart +++ b/lib/features/email/data/network/email_api.dart @@ -253,4 +253,28 @@ class EmailAPI { throw error; }); } + + Future saveEmailAsDrafts(AccountId accountId, Email email) async { + final setEmailMethod = SetEmailMethod(accountId) + ..addCreate(email.id.id, email); + + 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 { + return setEmailResponse?.created?[email.id.id] != 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 69117c7d5..677ad7c60 100644 --- a/lib/features/email/data/repository/email_repository_impl.dart +++ b/lib/features/email/data/repository/email_repository_impl.dart @@ -88,4 +88,9 @@ class EmailRepositoryImpl extends EmailRepository { }) .toList()); } + + @override + Future saveEmailAsDrafts(AccountId accountId, Email email) { + return emailDataSource.saveEmailAsDrafts(accountId, email); + } } \ 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 0dd43565e..2db16ed73 100644 --- a/lib/features/email/domain/repository/email_repository.dart +++ b/lib/features/email/domain/repository/email_repository.dart @@ -41,4 +41,6 @@ abstract class EmailRepository { String? baseUrlDownload, AccountId accountId ); + + Future saveEmailAsDrafts(AccountId accountId, Email email); } \ 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 355db2b50..78bc530ea 100644 --- a/lib/features/email/presentation/email_bindings.dart +++ b/lib/features/email/presentation/email_bindings.dart @@ -1,7 +1,6 @@ import 'package:core/core.dart'; import 'package:get/get.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; import 'package:tmail_ui_user/features/email/data/datasource/email_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart'; import 'package:tmail_ui_user/features/email/data/datasource_impl/email_datasource_impl.dart'; @@ -33,7 +32,6 @@ class EmailBindings extends Bindings { Get.find(), Get.find())); Get.lazyPut(() => Get.find()); - Get.put(SendEmailInteractor(Get.find())); Get.lazyPut(() => GetEmailContentInteractor( Get.find())); Get.lazyPut(() => MarkAsEmailReadInteractor(Get.find())); diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart index 77685321b..19ec74e36 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart @@ -7,6 +7,7 @@ import 'package:jmap_dart_client/jmap/core/session/session.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/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/email/presentation/email_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/get_user_profile_state.dart'; @@ -93,6 +94,8 @@ class MailboxDashBoardController extends BaseController { icon: _imagePaths.icSendToast); } clearState(); + } else if (success is SaveEmailAsDraftsSuccess) { + _saveEmailAsDraftsSuccess(success); } } ); @@ -190,6 +193,17 @@ class MailboxDashBoardController extends BaseController { FocusManager.instance.primaryFocus?.unfocus(); } + void _saveEmailAsDraftsSuccess(SaveEmailAsDraftsSuccess success) { + if (Get.context != null && Get.overlayContext != null) { + _appToast.showToastWithAction( + Get.overlayContext!, + AppLocalizations.of(Get.context!).drafts_saved, + AppLocalizations.of(Get.context!).discard, + () {} + ); + } + } + @override void onClose() { searchInputController.dispose(); diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 20343e851..5be1ec439 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -649,4 +649,18 @@ class AppLocalizations { name: 'new_mailbox_is_created' ); } + + String get drafts_saved { + return Intl.message( + 'Draft saved', + name: 'drafts_saved', + ); + } + + String get discard { + return Intl.message( + 'Discard', + name: 'discard' + ); + } } \ No newline at end of file