TF-2667 Show sending message dialog when click send button

This commit is contained in:
dab246
2024-03-05 22:57:51 +07:00
committed by Dat H. Pham
parent 016b75aa51
commit e232514f6b
37 changed files with 953 additions and 141 deletions
+1
View File
@@ -2,6 +2,7 @@ class Constant {
static const acceptHeaderDefault = 'application/json';
static const contentTypeHeaderDefault = 'application/json';
static const pdfMimeType = 'application/pdf';
static const base64Charset = 'base64';
static const textHtmlMimeType = 'text/html';
static const octetStreamMimeType = 'application/octet-stream';
static const pdfExtension = '.pdf';
+1
View File
@@ -34,6 +34,7 @@ class ApplicationManager {
userAgent = FkUserAgent.userAgent ?? '';
FkUserAgent.release();
}
log('ApplicationManager::getUserAgent: $userAgent');
return userAgent;
} catch(e) {
logError('ApplicationManager::getUserAgent: Exception: $e');
@@ -2,7 +2,7 @@ import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/utils/application_manager.dart';
import 'package:flutter/material.dart';
class ApplicationVersionWidget extends StatelessWidget {
class ApplicationVersionWidget extends StatefulWidget {
final ApplicationManager applicationManager;
final EdgeInsetsGeometry? padding;
@@ -17,18 +17,32 @@ class ApplicationVersionWidget extends StatelessWidget {
this.padding,
});
@override
State<ApplicationVersionWidget> createState() => _ApplicationVersionWidgetState();
}
class _ApplicationVersionWidgetState extends State<ApplicationVersionWidget> {
Future<String>? _versionStream;
@override
void initState() {
super.initState();
_versionStream = widget.applicationManager.getVersion();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: applicationManager.getVersion(),
future: _versionStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Padding(
padding: padding ?? const EdgeInsets.only(top: 6),
padding: widget.padding ?? const EdgeInsets.only(top: 6),
child: Text(
'${title ?? 'v.'}${snapshot.data}',
'${widget.title ?? 'v.'}${snapshot.data}',
textAlign: TextAlign.center,
style: textStyle ?? Theme.of(context).textTheme.labelMedium?.copyWith(
style: widget.textStyle ?? Theme.of(context).textTheme.labelMedium?.copyWith(
fontSize: 13,
color: AppColor.colorContentEmail,
fontWeight: FontWeight.w500
@@ -1,19 +1,37 @@
import 'package:core/data/constants/constant.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/application_manager.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/composer/data/datasource/composer_datasource.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/create_email_request_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart';
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
import 'package:tmail_ui_user/features/upload/data/datasource/attachment_upload_datasource.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
import 'package:uuid/uuid.dart';
class ComposerRepositoryImpl extends ComposerRepository {
final AttachmentUploadDataSource _attachmentUploadDataSource;
final ComposerDataSource _composerDataSource;
final HtmlDataSource _htmlDataSource;
final ApplicationManager _applicationManager;
final Uuid _uuid;
ComposerRepositoryImpl(
this._attachmentUploadDataSource,
this._composerDataSource);
this._composerDataSource,
this._htmlDataSource,
this._applicationManager,
this._uuid,
);
@override
Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken}) {
@@ -24,4 +42,62 @@ class ComposerRepositoryImpl extends ComposerRepository {
Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress}) {
return _composerDataSource.downloadImageAsBase64(url, cid, fileInfo, maxWidth: maxWidth, compress: compress);
}
@override
Future<Email> generateEmail(CreateEmailRequest createEmailRequest) async {
String emailContent = createEmailRequest.emailContent;
Set<EmailBodyPart> emailAttachments = Set.from(createEmailRequest.createAttachments());
if (createEmailRequest.inlineAttachments?.isNotEmpty == true) {
final tupleContentInlineAttachments = await _replaceImageBase64ToImageCID(
emailContent: emailContent,
inlineAttachments: createEmailRequest.inlineAttachments!
);
emailContent = tupleContentInlineAttachments.value1;
emailAttachments.addAll(tupleContentInlineAttachments.value2);
}
emailContent = await _removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
final userAgent = await _applicationManager.generateApplicationUserAgent();
final emailBodyPartId = PartId(_uuid.v1());
final emailObject = createEmailRequest.generateEmail(
newEmailContent: emailContent,
newEmailAttachments: emailAttachments,
userAgent: userAgent,
partId: emailBodyPartId
);
return emailObject;
}
Future<Tuple2<String, Set<EmailBodyPart>>> _replaceImageBase64ToImageCID({
required String emailContent,
required Map<String, Attachment> inlineAttachments
}) {
try {
return _htmlDataSource.replaceImageBase64ToImageCID(
emailContent: emailContent,
inlineAttachments: inlineAttachments);
} catch (e) {
logError('ComposerRepositoryImpl::_replaceImageBase64ToImageCID: Exception: $e');
return Future.value(
Tuple2(
emailContent,
inlineAttachments.values.toList().toEmailBodyPart(charset: Constant.base64Charset)
)
);
}
}
Future<String> _removeCollapsedExpandedSignatureEffect({required String emailContent}) {
try {
return _htmlDataSource.removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
} catch (e) {
logError('ComposerRepositoryImpl::_removeCollapsedExpandedSignatureEffect: Exception: $e');
return Future.value(emailContent);
}
}
}
@@ -1,8 +1,12 @@
import 'package:dio/dio.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/upload/file_info.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_attachment.dart';
abstract class ComposerRepository {
Future<Email> generateEmail(CreateEmailRequest createEmailRequest);
Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken});
Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress});
@@ -0,0 +1,9 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
class GenerateEmailLoading extends LoadingState {}
class GenerateEmailFailure extends FeatureFailure {
GenerateEmailFailure(dynamic exception) : super(exception: exception);
}
@@ -8,7 +8,7 @@ import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart'
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_action_type.dart';
class SendEmailLoading extends UIState {}
class SendEmailLoading extends LoadingState {}
class SendEmailSuccess extends UIActionState {
@@ -29,17 +29,17 @@ class SendEmailSuccess extends UIActionState {
class SendEmailFailure extends FeatureFailure {
final Session session;
final AccountId accountId;
final EmailRequest emailRequest;
final Session? session;
final AccountId? accountId;
final EmailRequest? emailRequest;
final CreateNewMailboxRequest? mailboxRequest;
final SendingEmailActionType? sendingEmailActionType;
SendEmailFailure({
dynamic exception,
required this.session,
required this.accountId,
required this.emailRequest,
this.session,
this.accountId,
this.emailRequest,
this.mailboxRequest,
this.sendingEmailActionType
}) : super(exception: exception);
@@ -0,0 +1,128 @@
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/send_email_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/create_email_request_extension.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';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
class CreateNewAndSendEmailInteractor {
final EmailRepository _emailRepository;
final MailboxRepository _mailboxRepository;
final ComposerRepository _composerRepository;
CreateNewAndSendEmailInteractor(
this._emailRepository,
this._mailboxRepository,
this._composerRepository,
);
Stream<dartz.Either<Failure, Success>> execute(CreateEmailRequest createEmailRequest) async* {
SendingEmailArguments? sendingEmailArguments;
try {
yield dartz.Right<Failure, Success>(GenerateEmailLoading());
final listCurrentState = await _getStoredCurrentState(
session: createEmailRequest.session,
accountId: createEmailRequest.accountId
);
sendingEmailArguments = await _createEmailObject(createEmailRequest);
if (sendingEmailArguments != null) {
yield dartz.Right<Failure, Success>(SendEmailLoading());
await _emailRepository.sendEmail(
sendingEmailArguments.session,
sendingEmailArguments.accountId,
sendingEmailArguments.emailRequest,
mailboxRequest: sendingEmailArguments.mailboxRequest
);
if (sendingEmailArguments.emailRequest.emailIdDestroyed != null) {
await _deleteOldDraftsEmail(
session: sendingEmailArguments.session,
accountId: sendingEmailArguments.accountId,
draftEmailId: sendingEmailArguments.emailRequest.emailIdDestroyed!
);
}
yield dartz.Right<Failure, Success>(
SendEmailSuccess(
currentMailboxState: listCurrentState?.value1,
currentEmailState: listCurrentState?.value2,
emailRequest: sendingEmailArguments.emailRequest
)
);
} else {
yield dartz.Left<Failure, Success>(GenerateEmailFailure(CannotCreateEmailObjectException()));
}
} catch (e) {
logError('CreateNewAndSendEmailInteractor::execute: Exception: $e');
yield dartz.Left<Failure, Success>(SendEmailFailure(
exception: e,
session: sendingEmailArguments?.session,
accountId: sendingEmailArguments?.accountId,
emailRequest: sendingEmailArguments?.emailRequest,
mailboxRequest: sendingEmailArguments?.mailboxRequest,
));
}
}
Future<SendingEmailArguments?> _createEmailObject(CreateEmailRequest createEmailRequest) async {
try {
final emailCreated = await _composerRepository.generateEmail(createEmailRequest);
final sendingEmailArgument = createEmailRequest.toSendingEmailArguments(emailObject: emailCreated);
return sendingEmailArgument;
} catch (e) {
logError('CreateNewAndSendEmailInteractor::_createEmailObject: Exception: $e');
return null;
}
}
Future<dartz.Tuple2<State?, State?>?> _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('CreateNewAndSendEmailInteractor::_getStoredCurrentState: Exception: $e');
return null;
}
}
Future<void> _deleteOldDraftsEmail({
required Session session,
required AccountId accountId,
required EmailId draftEmailId
}) async {
try {
await _emailRepository.deleteEmailPermanently(
session,
accountId,
draftEmailId
);
} catch (e) {
logError('CreateNewAndSendEmailInteractor::_deleteOldDraftsEmail: Exception: $e');
}
}
}
@@ -38,34 +38,24 @@ class SendEmailInteractor {
final currentMailboxState = listState.first;
final currentEmailState = listState.last;
final result = await _emailRepository.sendEmail(
await _emailRepository.sendEmail(
session,
accountId,
emailRequest,
mailboxRequest: mailboxRequest
);
if (result) {
if (emailRequest.emailIdDestroyed != null) {
await _emailRepository.deleteEmailPermanently(session, accountId, emailRequest.emailIdDestroyed!);
}
yield Right<Failure, Success>(
SendEmailSuccess(
currentEmailState: currentEmailState,
currentMailboxState: currentMailboxState,
emailRequest: emailRequest
)
);
} else {
yield Left<Failure, Success>(SendEmailFailure(
session: session,
accountId: accountId,
emailRequest: emailRequest,
mailboxRequest: mailboxRequest,
sendingEmailActionType: sendingEmailActionType,
));
if (emailRequest.emailIdDestroyed != null) {
await _emailRepository.deleteEmailPermanently(session, accountId, emailRequest.emailIdDestroyed!);
}
yield Right<Failure, Success>(
SendEmailSuccess(
currentEmailState: currentEmailState,
currentMailboxState: currentMailboxState,
emailRequest: emailRequest
)
);
} catch (e) {
yield Left<Failure, Success>(SendEmailFailure(
exception: e,
@@ -1,5 +1,6 @@
import 'package:core/core.dart';
import 'package:device_info_plus/device_info_plus.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';
@@ -10,6 +11,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_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';
import 'package:tmail_ui_user/features/composer/domain/usecases/update_email_drafts_interactor.dart';
@@ -104,7 +106,7 @@ class ComposerBindings extends BaseBindings {
Get.find<RemoteExceptionThrower>()));
Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<RemoteExceptionThrower>()));
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => StateDataSourceImpl(
Get.find<StateCacheManager>(),
Get.find<IOSSharingManager>(),
@@ -147,8 +149,12 @@ class ComposerBindings extends BaseBindings {
@override
void bindingsRepositoryImpl() {
Get.lazyPut(() => ComposerRepositoryImpl(
Get.find<AttachmentUploadDataSource>(),
Get.find<ComposerDataSource>()));
Get.find<AttachmentUploadDataSource>(),
Get.find<ComposerDataSource>(),
Get.find<HtmlDataSource>(),
Get.find<ApplicationManager>(),
Get.find<Uuid>(),
));
Get.lazyPut(() => ContactRepositoryImpl(Get.find<ContactDataSource>()));
Get.lazyPut(() => MailboxRepositoryImpl(
{
@@ -197,6 +203,11 @@ class ComposerBindings extends BaseBindings {
Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find<ComposerRepository>()));
Get.lazyPut(() => TransformHtmlEmailContentInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => GetAlwaysReadReceiptSettingInteractor(Get.find<ServerSettingsRepository>()));
Get.lazyPut(() => CreateNewAndSendEmailInteractor(
Get.find<EmailRepository>(),
Get.find<MailboxRepository>(),
Get.find<ComposerRepository>(),
));
IdentityInteractorsBindings().dependencies();
}
@@ -17,7 +17,6 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart' as web_html_editor;
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
@@ -29,6 +28,7 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.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:pointer_interceptor/pointer_interceptor.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:super_tag_editor/tag_editor.dart';
@@ -36,12 +36,12 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
import 'package:tmail_ui_user/features/base/state/button_state.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/get_device_contact_suggestions_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_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';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
@@ -53,6 +53,7 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/file_upl
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_shared_media_file_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/drag_drog_file_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/draggable_email_address.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
@@ -60,13 +61,13 @@ 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/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';
import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
@@ -76,7 +77,6 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_id
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/identity_extension.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/extensions/sending_email_extension.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
import 'package:tmail_ui_user/features/server_settings/domain/state/get_always_read_receipt_setting_state.dart';
@@ -131,6 +131,7 @@ class ComposerController extends BaseController with DragDropFileMixin {
final DownloadImageAsBase64Interactor _downloadImageAsBase64Interactor;
final TransformHtmlEmailContentInteractor _transformHtmlEmailContentInteractor;
final GetAlwaysReadReceiptSettingInteractor _getAlwaysReadReceiptSettingInteractor;
final CreateNewAndSendEmailInteractor _createNewAndSendEmailInteractor;
GetAllAutoCompleteInteractor? _getAllAutoCompleteInteractor;
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
@@ -198,6 +199,7 @@ class ComposerController extends BaseController with DragDropFileMixin {
this._downloadImageAsBase64Interactor,
this._transformHtmlEmailContentInteractor,
this._getAlwaysReadReceiptSettingInteractor,
this._createNewAndSendEmailInteractor,
);
@override
@@ -760,7 +762,6 @@ class ComposerController extends BaseController with DragDropFileMixin {
bodyValues: {
generatePartId: EmailBodyValue(emailBodyText, false, false)
},
headerUserAgent: {IndividualHeaderIdentifier.headerUserAgent : userAgent},
attachments: attachments.isNotEmpty ? attachments : null,
headerMdn: hasRequestReadReceipt.value ? { IndividualHeaderIdentifier.headerMdn: getEmailAddressSender() } : {},
);
@@ -899,67 +900,64 @@ class ComposerController extends BaseController with DragDropFileMixin {
_handleSendMessages(context);
}
bool get _isParamUserNull {
Future<String> _getContentInEditor() async {
final htmlTextEditor = PlatformInfo.isWeb
? _textEditorWeb
: await htmlEditorApi?.getText();
if (htmlTextEditor?.isNotEmpty == true) {
return htmlTextEditor!.removeEditorStartTag();
} else {
return '';
}
}
void _handleSendMessages(BuildContext context) async {
if (composerArguments.value == null ||
mailboxDashBoardController.sessionCurrent == null ||
mailboxDashBoardController.accountId.value == null
) {
logError('ComposerController::isParamUserNotNull: Param is NULL');
return true;
}
return false;
}
void _handleSendMessages(BuildContext context) async {
if (_isParamUserNull) {
logError('ComposerController::_handleSendMessages: Param is NULL');
log('ComposerController::_handleSendMessages: SESSION or ACCOUNT_ID or ARGUMENTS is NULL');
_closeComposerAction();
return;
}
final sendingArgs = await createSendingEmailArguments(context);
_closeComposerAction(result: sendingArgs);
}
final emailContent = await _getContentInEditor();
Future<SendingEmailArguments> createSendingEmailArguments(BuildContext context) async {
final session = mailboxDashBoardController.sessionCurrent!;
final arguments = composerArguments.value!;
final accountId = mailboxDashBoardController.accountId.value!;
final createdEmail = await _generateEmail(
context,
session.username,
outboxMailboxId: mailboxDashBoardController.outboxMailbox?.id,
arguments: arguments
final resultState = await Get.dialog(
PointerInterceptor(
child: SendingMessageDialogView(
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,
outboxMailboxId: mailboxDashBoardController.outboxMailbox?.mailboxId,
sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent],
draftsEmailId: composerArguments.value!.emailActionType == EmailActionType.editDraft
? composerArguments.value!.presentationEmail?.id
: null,
answerForwardEmailId: composerArguments.value!.presentationEmail?.id,
unsubscribeEmailId: composerArguments.value!.previousEmailId,
messageId: composerArguments.value!.messageId,
references: composerArguments.value!.references,
emailSendingQueue: composerArguments.value!.sendingEmail
),
createNewAndSendEmailInteractor: _createNewAndSendEmailInteractor
),
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
final emailRequest = arguments.emailActionType == EmailActionType.editSendingEmail
? arguments.sendingEmail!.toEmailRequest(newEmail: createdEmail)
: EmailRequest(
email: createdEmail,
sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent],
identityId: identitySelected.value?.id,
emailIdDestroyed: arguments.emailActionType == EmailActionType.editDraft
? arguments.presentationEmail?.id
: null,
emailIdAnsweredOrForwarded: arguments.presentationEmail?.id,
emailActionType: arguments.emailActionType,
previousEmailId: arguments.previousEmailId,
);
final mailboxRequest = mailboxDashBoardController.outboxMailbox?.id == null
? CreateNewMailboxRequest(
Id(uuid.v1()),
MailboxName(PresentationMailbox.outboxRole.inCaps)
)
: null;
return SendingEmailArguments(
session,
accountId,
emailRequest,
mailboxRequest,
);
_closeComposerAction(result: resultState);
}
void _checkContactPermission() async {
@@ -1370,7 +1368,6 @@ class ComposerController extends BaseController with DragDropFileMixin {
}
void _closeComposerAction({dynamic result}) {
log('ComposerController::_closeComposerAction:');
isSendEmailLoading.value = false;
if (PlatformInfo.isWeb) {
@@ -0,0 +1,176 @@
import 'package:core/core.dart';
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/extensions/email_address_extension.dart';
import 'package:model/extensions/username_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/identity_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/extensions/sending_email_extension.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
extension CreateEmailRequestExtension on CreateEmailRequest {
Set<EmailAddress> createSenders() {
if (identity?.email?.isNotEmpty == true) {
return { identity!.toEmailAddress() };
} else {
return { session.username.toEmailAddress() };
}
}
String createMdnEmailAddress() {
if (emailActionType == EmailActionType.editDraft && fromSender.isNotEmpty) {
return fromSender.first.emailAddress;
} else {
return session.username.value;
}
}
Set<EmailAddress> createReplyToRecipients() {
if (identity?.replyTo?.isNotEmpty == true) {
return identity!.replyTo!.toSet();
} else {
return { session.username.toEmailAddress() };
}
}
Set<EmailBodyPart> createAttachments() => attachments?.toEmailBodyPart() ?? {};
Map<KeyWordIdentifier, bool>? createKeywords() {
if (draftsMailboxId != null) {
return {
KeyWordIdentifier.emailDraft: true,
KeyWordIdentifier.emailSeen: true,
};
} else {
return null;
}
}
Map<MailboxId, bool>? createMailboxIds() {
if (draftsMailboxId != null || outboxMailboxId != null) {
return {
if (draftsMailboxId != null)
draftsMailboxId!: true,
if (outboxMailboxId != null)
outboxMailboxId!: true,
};
} else {
return null;
}
}
MessageIdsHeaderValue? createInReplyTo() {
if (emailActionType == EmailActionType.reply ||
emailActionType == EmailActionType.replyAll
) {
return messageId;
}
return null;
}
MessageIdsHeaderValue? createReferences() {
if (emailActionType == EmailActionType.reply ||
emailActionType == EmailActionType.replyAll ||
emailActionType == EmailActionType.forward
) {
Set<String> ids = {};
if (messageId?.ids.isNotEmpty == true) {
ids.addAll(messageId!.ids);
}
if (references?.ids.isNotEmpty == true) {
ids.addAll(references!.ids);
}
if (ids.isNotEmpty) {
return MessageIdsHeaderValue(ids);
}
}
return null;
}
Email generateEmail({
required String newEmailContent,
required Set<EmailBodyPart> newEmailAttachments,
required String userAgent,
required PartId partId,
}) {
return Email(
mailboxIds: createMailboxIds(),
from: createSenders(),
to: toRecipients,
cc: ccRecipients,
bcc: bccRecipients,
replyTo: createReplyToRecipients(),
inReplyTo: createInReplyTo(),
references: createReferences(),
keywords: createKeywords(),
subject: subject,
htmlBody: {
EmailBodyPart(
partId: partId,
type: MediaType.parse(Constant.textHtmlMimeType)
)
},
bodyValues: {
partId: EmailBodyValue(
newEmailContent,
false,
false
)
},
headerUserAgent: {
IndividualHeaderIdentifier.headerUserAgent : userAgent
},
attachments: newEmailAttachments.isNotEmpty
? newEmailAttachments
: null,
headerMdn: isRequestReadReceipt
? { IndividualHeaderIdentifier.headerMdn: createMdnEmailAddress() }
: null,
);
}
EmailRequest createEmailRequest({required Email emailObject}) {
if (emailActionType == EmailActionType.editSendingEmail) {
return emailSendingQueue!.toEmailRequest(newEmail: emailObject);
} else {
return EmailRequest(
email: emailObject,
sentMailboxId: sentMailboxId,
identityId: identity?.id,
emailIdDestroyed: draftsEmailId,
emailIdAnsweredOrForwarded: answerForwardEmailId,
emailActionType: emailActionType,
previousEmailId: unsubscribeEmailId,
);
}
}
CreateNewMailboxRequest? createMailboxRequest() {
if (outboxMailboxId == null) {
return CreateNewMailboxRequest(MailboxName(PresentationMailbox.outboxRole.inCaps));
} else {
return null;
}
}
SendingEmailArguments toSendingEmailArguments({required Email emailObject}) {
return SendingEmailArguments(
session,
accountId,
createEmailRequest(emailObject: emailObject),
createMailboxRequest()
);
}
}
@@ -0,0 +1,6 @@
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
extension IdentityExtension on Identity {
EmailAddress toEmailAddress() => EmailAddress(name, email);
}
@@ -0,0 +1,88 @@
import 'package:equatable/equatable.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/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_action_type.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
class CreateEmailRequest with EquatableMixin {
final Session session;
final AccountId accountId;
final EmailActionType emailActionType;
final String subject;
final String emailContent;
final bool isRequestReadReceipt;
final Set<EmailAddress> fromSender;
final Set<EmailAddress> toRecipients;
final Set<EmailAddress> ccRecipients;
final Set<EmailAddress> bccRecipients;
final Identity? identity;
final List<Attachment>? attachments;
final Map<String, Attachment>? inlineAttachments;
final MailboxId? outboxMailboxId;
final MailboxId? sentMailboxId;
final MailboxId? draftsMailboxId;
final EmailId? draftsEmailId;
final EmailId? answerForwardEmailId;
final EmailId? unsubscribeEmailId;
final MessageIdsHeaderValue? messageId;
final MessageIdsHeaderValue? references;
final SendingEmail? emailSendingQueue;
CreateEmailRequest({
required this.session,
required this.accountId,
required this.emailActionType,
required this.subject,
required this.emailContent,
required this.fromSender,
required this.toRecipients,
required this.ccRecipients,
required this.bccRecipients,
this.isRequestReadReceipt = true,
this.identity,
this.attachments,
this.inlineAttachments,
this.outboxMailboxId,
this.sentMailboxId,
this.draftsMailboxId,
this.draftsEmailId,
this.answerForwardEmailId,
this.unsubscribeEmailId,
this.messageId,
this.references,
this.emailSendingQueue
});
@override
List<Object?> get props => [
session,
accountId,
emailActionType,
subject,
emailContent,
fromSender,
toRecipients,
ccRecipients,
bccRecipients,
identity,
isRequestReadReceipt,
attachments,
inlineAttachments,
outboxMailboxId,
sentMailboxId,
draftsMailboxId,
draftsEmailId,
answerForwardEmailId,
unsubscribeEmailId,
references,
references,
emailSendingQueue
];
}
@@ -0,0 +1,206 @@
import 'dart:async';
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: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 SendingMessageDialogView extends StatefulWidget {
final CreateEmailRequest createEmailRequest;
final CreateNewAndSendEmailInteractor createNewAndSendEmailInteractor;
const SendingMessageDialogView({
super.key,
required this.createEmailRequest,
required this.createNewAndSendEmailInteractor,
});
@override
State<SendingMessageDialogView> createState() => _SendingMessageDialogViewState();
}
class _SendingMessageDialogViewState extends State<SendingMessageDialogView> {
StreamSubscription? _sendingStreamSubscription;
final ValueNotifier<dartz.Either<Failure, Success>?> _sendingNotifier = ValueNotifier(null);
@override
void initState() {
super.initState();
_sendingStreamSubscription = widget.createNewAndSendEmailInteractor
.execute(widget.createEmailRequest)
.listen(
_handleDataStream,
onError: _handleErrorStream
);
}
void _handleDataStream(dartz.Either<Failure, Success> 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: 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();
}
}
@@ -28,7 +28,7 @@ import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.
abstract class EmailDataSource {
Future<Email> getEmailContent(Session session, AccountId accountId, EmailId emailId);
Future<bool> sendEmail(
Future<void> sendEmail(
Session session,
AccountId accountId,
EmailRequest emailRequest,
@@ -1,6 +1,8 @@
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:model/model.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_content.dart';
abstract class HtmlDataSource {
Future<EmailContent> transformEmailContent(
@@ -13,4 +15,11 @@ abstract class HtmlDataSource {
String htmlContent,
TransformConfiguration configuration
);
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
required String emailContent,
required Map<String, Attachment> inlineAttachments
});
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent});
}
@@ -42,7 +42,7 @@ class EmailDataSourceImpl extends EmailDataSource {
}
@override
Future<bool> sendEmail(
Future<void> sendEmail(
Session session,
AccountId accountId,
EmailRequest emailRequest,
@@ -1,4 +1,7 @@
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_content.dart';
import 'package:tmail_ui_user/features/email/data/datasource/html_datasource.dart';
import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart';
@@ -35,4 +38,26 @@ class HtmlDataSourceImpl extends HtmlDataSource {
);
}).catchError(_exceptionThrower.throwException);
}
@override
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
required String emailContent,
required Map<String, Attachment> inlineAttachments
}) {
return Future.sync(() async {
return await _htmlAnalyzer.replaceImageBase64ToImageCID(
emailContent: emailContent,
inlineAttachments: inlineAttachments
);
}).catchError(_exceptionThrower.throwException);
}
@override
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) {
return Future.sync(() async {
return await _htmlAnalyzer.removeCollapsedExpandedSignatureEffect(
emailContent: emailContent
);
}).catchError(_exceptionThrower.throwException);
}
}
@@ -1,10 +1,15 @@
import 'package:collection/collection.dart';
import 'package:core/data/constants/constant.dart';
import 'package:core/presentation/utils/html_transformer/html_transform.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:html/parser.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/email/email_content.dart';
import 'package:model/email/email_content_type.dart';
import 'package:model/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
class HtmlAnalyzer {
@@ -99,4 +104,57 @@ class HtmlAnalyzer {
);
return htmlContentTransformed;
}
Future<Tuple2<String, Set<EmailBodyPart>>> replaceImageBase64ToImageCID({
required String emailContent,
required Map<String, Attachment> inlineAttachments
}) async {
final document = parse(emailContent);
final listImgTag = document.querySelectorAll('img[src^="data:image/"][id^="cid:"]');
final listInlineAttachment = await Future.wait(listImgTag.map((imgTag) async {
final idImg = imgTag.attributes['id'];
final cid = idImg!.replaceFirst('cid:', '').trim();
imgTag.attributes['src'] = 'cid:$cid';
imgTag.attributes.remove('id');
return cid;
})).then((listCid) {
final listInlineAttachment = listCid
.map((cid) {
if (inlineAttachments.containsKey(cid)) {
return inlineAttachments[cid]!.toEmailBodyPart(charset: Constant.base64Charset);
} else {
return null;
}
})
.whereNotNull()
.toSet();
return listInlineAttachment;
});
final newContent = document.body?.innerHtml ?? emailContent;
return Tuple2(newContent, listInlineAttachment);
}
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) async {
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: BEFORE = $emailContent');
final document = parse(emailContent);
final signatureElements = document.querySelectorAll('div.tmail-signature');
await Future.wait(signatureElements.map((signatureTag) async {
final signatureChildren = signatureTag.children;
for (var child in signatureChildren) {
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: CHILD = ${child.outerHtml}');
if (child.attributes['class']?.contains('tmail-signature-button') == true) {
child.remove();
} else if (child.attributes['class']?.contains('tmail-signature-content') == true) {
signatureTag.innerHtml = child.innerHtml;
}
}
}));
final newContent = document.body?.innerHtml ?? emailContent;
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: AFTER = $newContent');
return newContent;
}
}
@@ -54,7 +54,7 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
Future<bool> sendEmail(
Future<void> sendEmail(
Session session,
AccountId accountId,
EmailRequest emailRequest,
@@ -6,4 +6,6 @@ class EmptyEmailContentException implements Exception {}
class NotFoundEmailRecoveryActionException implements Exception {}
class NotFoundEmailBlobIdException implements Exception {}
class NotFoundEmailBlobIdException implements Exception {}
class CannotCreateEmailObjectException implements Exception {}
@@ -1,7 +1,9 @@
import 'package:collection/collection.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/email/attachment.dart';
import 'package:model/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/email/domain/extensions/attachment_extension.dart';
import 'package:tmail_ui_user/features/offline_mode/model/attachment_hive_cache.dart';
@@ -19,4 +21,6 @@ extension ListAttachmentsExtension on List<Attachment> {
.map((attachment) => attachment.blobId)
.whereNotNull()
.toSet();
Set<EmailBodyPart> toEmailBodyPart({String? charset}) => map((attachment) => attachment.toEmailBodyPart(charset: charset)).toSet();
}
@@ -30,7 +30,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_r
abstract class EmailRepository {
Future<Email> getEmailContent(Session session, AccountId accountId, EmailId emailId);
Future<bool> sendEmail(
Future<void> sendEmail(
Session session,
AccountId accountId,
EmailRequest emailRequest,
@@ -96,7 +96,7 @@ class EmailBindings extends BaseBindings {
Get.find<RemoteExceptionThrower>()));
Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<RemoteExceptionThrower>()));
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => StateDataSourceImpl(
Get.find<StateCacheManager>(),
Get.find<IOSSharingManager>(),
@@ -204,7 +204,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.find<RemoteExceptionThrower>()));
Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<RemoteExceptionThrower>()));
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => SearchDataSourceImpl(
Get.find<RecentSearchCacheClient>(),
Get.find<CacheExceptionThrower>()));
@@ -30,6 +30,7 @@ import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dar
import 'package:tmail_ui_user/features/composer/domain/exceptions/set_method_exception.dart';
import 'package:tmail_ui_user/features/composer/domain/extensions/email_request_extension.dart';
import 'package:tmail_ui_user/features/composer/domain/model/email_request.dart';
import 'package:tmail_ui_user/features/composer/domain/state/generate_email_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/send_email_state.dart';
@@ -1256,11 +1257,14 @@ class MailboxDashBoardController extends ReloadableController {
composerArguments = null;
ComposerBindings().dispose();
composerOverlayState.value = ComposerOverlayState.inActive;
if (result is SendingEmailArguments) {
handleSendEmailAction(result);
} else if (result is SaveToDraftArguments) {
saveEmailToDraft(arguments: result);
} else if (result is SendEmailSuccess) {
consumeState(Stream.value(Right<Failure, Success>(result)));
} else if (result is SendEmailFailure || result is GenerateEmailFailure) {
consumeState(Stream.value(Left<Failure, Success>(result)));
}
}
@@ -1394,6 +1398,10 @@ class MailboxDashBoardController extends ReloadableController {
handleSendEmailAction(result);
} else if (result is SaveToDraftArguments) {
saveEmailToDraft(arguments: result);
} else if (result is SendEmailSuccess) {
consumeState(Stream.value(Right<Failure, Success>(result)));
} else if (result is SendEmailFailure || result is GenerateEmailFailure) {
consumeState(Stream.value(Left<Failure, Success>(result)));
}
}
}
@@ -132,7 +132,10 @@ class ManageAccountDashBoardView extends GetWidget<ManageAccountDashBoardControl
const Spacer(),
const SizedBox(width: 16),
Obx(() => (AvatarBuilder()
..text(controller.sessionCurrent?.username.firstCharacter ?? '')
..text(controller.accountId.value != null
? controller.sessionCurrent?.username.firstCharacter ?? ''
: ''
)
..backgroundColor(Colors.white)
..textColor(Colors.black)
..context(context)
@@ -94,4 +94,6 @@ class NetworkConnectionController extends GetxController {
);
}
}
Future<bool> hasInternetConnection() async => isNetworkConnectionAvailable();
}
@@ -68,7 +68,7 @@ class SendEmailInteractorBindings extends InteractorsBindings {
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<RemoteExceptionThrower>()));
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => StateDataSourceImpl(
Get.find<StateCacheManager>(),
Get.find<IOSSharingManager>(),
@@ -105,7 +105,7 @@ class FcmInteractorBindings extends InteractorsBindings {
Get.find<RemoteExceptionThrower>()));
Get.lazyPut(() => HtmlDataSourceImpl(
Get.find<HtmlAnalyzer>(),
Get.find<RemoteExceptionThrower>()));
Get.find<CacheExceptionThrower>()));
Get.lazyPut(() => StateDataSourceImpl(
Get.find<StateCacheManager>(),
Get.find<IOSSharingManager>(),
@@ -255,8 +255,7 @@ class UploadController extends BaseController {
return null;
}
return attachmentsUploaded
.map((attachment) => attachment.toEmailBodyPart(
disposition: ContentDisposition.attachment.value))
.map((attachment) => attachment.toEmailBodyPart())
.toSet();
}
@@ -1,7 +1,8 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -38,17 +38,22 @@ class LocalizationService extends Translations {
}
static Locale getLocaleFromLanguage({String? langCode}) {
final languageCacheManager = getBinding<LanguageCacheManager>();
log('LocalizationService::_getLocaleFromLanguage:languageCacheManager: $languageCacheManager');
final localeStored = languageCacheManager?.getStoredLanguage();
log('LocalizationService::_getLocaleFromLanguage():localeStored: $localeStored');
if (localeStored != null) {
return localeStored;
} else {
final languageCodeCurrent = langCode ?? Get.deviceLocale?.languageCode;
log('LocalizationService::_getLocaleFromLanguage():languageCodeCurrent: $languageCodeCurrent');
final localeSelected = supportedLocales.firstWhereOrNull((locale) => locale.languageCode == languageCodeCurrent);
return localeSelected ?? Get.deviceLocale ?? defaultLocale;
try {
final languageCacheManager = getBinding<LanguageCacheManager>();
log('LocalizationService::_getLocaleFromLanguage:languageCacheManager: $languageCacheManager');
final localeStored = languageCacheManager?.getStoredLanguage();
log('LocalizationService::_getLocaleFromLanguage():localeStored: $localeStored');
if (localeStored != null) {
return localeStored;
} else {
final languageCodeCurrent = langCode ?? Get.deviceLocale?.languageCode;
log('LocalizationService::_getLocaleFromLanguage():languageCodeCurrent: $languageCodeCurrent');
final localeSelected = supportedLocales.firstWhereOrNull((locale) => locale.languageCode == languageCodeCurrent);
return localeSelected ?? Get.deviceLocale ?? defaultLocale;
}
} catch (e) {
logError('LocalizationService::getLocaleFromLanguage: Exception: $e');
return Get.deviceLocale ?? defaultLocale;
}
}
-13
View File
@@ -68,19 +68,6 @@ enum ContentDisposition {
other
}
extension ContentDispositionExtension on ContentDisposition {
String get value {
switch(this) {
case ContentDisposition.inline:
return 'inline';
case ContentDisposition.attachment:
return 'attachment';
case ContentDisposition.other:
return toString();
}
}
}
extension DispositionStringExtension on String? {
ContentDisposition? toContentDisposition() {
if (this != null) {
@@ -1,9 +1,10 @@
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:model/model.dart';
import 'package:model/email/attachment.dart';
import 'package:model/extensions/list_attachment_extension.dart';
extension AttachmentExtension on Attachment {
EmailBodyPart toEmailBodyPart({String? disposition, String? charset}) => EmailBodyPart(
EmailBodyPart toEmailBodyPart({String? charset}) => EmailBodyPart(
partId: partId,
blobId: blobId,
size: size,
@@ -11,7 +12,7 @@ extension AttachmentExtension on Attachment {
type: type,
cid: cid,
charset: charset,
disposition: disposition ?? this.disposition?.value);
disposition: disposition?.name ?? ContentDisposition.attachment.name);
Attachment toAttachmentWithDisposition({
ContentDisposition? disposition,
+4 -3
View File
@@ -1,7 +1,8 @@
import 'package:core/presentation/extensions/string_extension.dart';
import 'package:jmap_dart_client/jmap/core/user_name.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
extension UsernameExtension on UserName {
String get firstCharacter => value.firstLetterToUpperCase.toUpperCase();
String get firstCharacter => value.isNotEmpty ? value[0].toUpperCase() : '';
EmailAddress toEmailAddress() => EmailAddress(null, value);
}