TF-825 Download email as EML file (#2854)
This commit is contained in:
@@ -3,4 +3,5 @@ class Constant {
|
|||||||
static const contentTypeHeaderDefault = 'application/json';
|
static const contentTypeHeaderDefault = 'application/json';
|
||||||
static const pdfMimeType = 'application/pdf';
|
static const pdfMimeType = 'application/pdf';
|
||||||
static const textHtmlMimeType = 'text/html';
|
static const textHtmlMimeType = 'text/html';
|
||||||
|
static const octetStreamMimeType = 'application/octet-stream';
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
|
||||||
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_database_steps.dart';
|
||||||
|
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||||
|
|
||||||
|
class UpgradeHiveDatabaseStepsV11 extends UpgradeDatabaseSteps {
|
||||||
|
|
||||||
|
final CachingManager _cachingManager;
|
||||||
|
|
||||||
|
UpgradeHiveDatabaseStepsV11(this._cachingManager);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onUpgrade(int oldVersion, int newVersion) async {
|
||||||
|
if (oldVersion > 0 && oldVersion < newVersion && newVersion == 11) {
|
||||||
|
await _cachingManager.clearEmailCacheAndAllStateCache();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
|
|
||||||
class CacheVersion {
|
class CacheVersion {
|
||||||
static const int hiveDBVersion = 10;
|
static const int hiveDBVersion = 11;
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ import 'package:core/utils/platform_info.dart';
|
|||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:path_provider/path_provider.dart' as path_provider;
|
import 'package:path_provider/path_provider.dart' as path_provider;
|
||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v10.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v10.dart';
|
||||||
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v11.dart';
|
||||||
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart';
|
import 'package:tmail_ui_user/features/base/upgradeable/upgrade_hive_database_steps_v7.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
import 'package:tmail_ui_user/features/caching/caching_manager.dart';
|
||||||
import 'package:tmail_ui_user/features/caching/config/cache_version.dart';
|
import 'package:tmail_ui_user/features/caching/config/cache_version.dart';
|
||||||
@@ -65,8 +66,11 @@ class HiveCacheConfig {
|
|||||||
|
|
||||||
await UpgradeHiveDatabaseStepsV7(cachingManager).onUpgrade(oldVersion, newVersion);
|
await UpgradeHiveDatabaseStepsV7(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
await UpgradeHiveDatabaseStepsV10(cachingManager).onUpgrade(oldVersion, newVersion);
|
await UpgradeHiveDatabaseStepsV10(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
|
await UpgradeHiveDatabaseStepsV11(cachingManager).onUpgrade(oldVersion, newVersion);
|
||||||
|
|
||||||
await cachingManager.storeCacheVersion(newVersion);
|
if (oldVersion != newVersion) {
|
||||||
|
await cachingManager.storeCacheVersion(newVersion);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initializeEncryptionKey() async {
|
Future<void> initializeEncryptionKey() async {
|
||||||
|
|||||||
@@ -133,6 +133,8 @@ extension EmailActionTypeExtension on EmailActionType {
|
|||||||
return imagePaths.icUnsubscribe;
|
return imagePaths.icUnsubscribe;
|
||||||
case EmailActionType.archiveMessage:
|
case EmailActionType.archiveMessage:
|
||||||
return imagePaths.icMailboxArchived;
|
return imagePaths.icMailboxArchived;
|
||||||
|
case EmailActionType.downloadMessageAsEML:
|
||||||
|
return imagePaths.icDownloadAttachment;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -152,6 +154,8 @@ extension EmailActionTypeExtension on EmailActionType {
|
|||||||
return AppLocalizations.of(context).unsubscribe;
|
return AppLocalizations.of(context).unsubscribe;
|
||||||
case EmailActionType.archiveMessage:
|
case EmailActionType.archiveMessage:
|
||||||
return AppLocalizations.of(context).archiveMessage;
|
return AppLocalizations.of(context).archiveMessage;
|
||||||
|
case EmailActionType.downloadMessageAsEML:
|
||||||
|
return AppLocalizations.of(context).downloadMessageAsEML;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -351,12 +351,12 @@ class EmailAPI with HandleSetErrorMixin {
|
|||||||
headers: headerParam,
|
headers: headerParam,
|
||||||
responseType: ResponseType.bytes),
|
responseType: ResponseType.bytes),
|
||||||
onReceiveProgress: (downloaded, total) {
|
onReceiveProgress: (downloaded, total) {
|
||||||
log('DownloadClient::downloadFileForWeb(): downloaded = $downloaded | total: $total');
|
log('EmailAPI::downloadFileForWeb(): downloaded = $downloaded | total: $total');
|
||||||
double progress = 0;
|
double progress = 0;
|
||||||
if (downloaded > 0 && total > downloaded) {
|
if (downloaded > 0 && total >= downloaded) {
|
||||||
progress = (downloaded / total) * 100;
|
progress = (downloaded / total) * 100;
|
||||||
}
|
}
|
||||||
log('DownloadClient::downloadFileForWeb(): progress = ${progress.round()}%');
|
log('EmailAPI::downloadFileForWeb(): progress = ${progress.round()}%');
|
||||||
onReceiveController.add(Right(DownloadingAttachmentForWeb(
|
onReceiveController.add(Right(DownloadingAttachmentForWeb(
|
||||||
taskId,
|
taskId,
|
||||||
attachment,
|
attachment,
|
||||||
|
|||||||
@@ -4,4 +4,6 @@ class NotFoundEmailContentException implements Exception {}
|
|||||||
|
|
||||||
class EmptyEmailContentException implements Exception {}
|
class EmptyEmailContentException implements Exception {}
|
||||||
|
|
||||||
class NotFoundEmailRecoveryActionException implements Exception {}
|
class NotFoundEmailRecoveryActionException implements Exception {}
|
||||||
|
|
||||||
|
class NotFoundEmailBlobIdException implements Exception {}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:core/presentation/state/failure.dart';
|
import 'package:core/presentation/state/failure.dart';
|
||||||
import 'package:core/presentation/state/success.dart';
|
import 'package:core/presentation/state/success.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
|
||||||
import 'package:model/download/download_task_id.dart';
|
import 'package:model/download/download_task_id.dart';
|
||||||
import 'package:model/email/attachment.dart';
|
import 'package:model/email/attachment.dart';
|
||||||
|
|
||||||
@@ -59,14 +58,14 @@ class DownloadAttachmentForWebSuccess extends UIState {
|
|||||||
class DownloadAttachmentForWebFailure extends FeatureFailure {
|
class DownloadAttachmentForWebFailure extends FeatureFailure {
|
||||||
|
|
||||||
final DownloadTaskId? taskId;
|
final DownloadTaskId? taskId;
|
||||||
final Id? attachmentBlobId;
|
final Attachment? attachment;
|
||||||
|
|
||||||
DownloadAttachmentForWebFailure({
|
DownloadAttachmentForWebFailure({
|
||||||
required this.attachmentBlobId,
|
this.attachment,
|
||||||
this.taskId,
|
this.taskId,
|
||||||
dynamic exception
|
dynamic exception
|
||||||
}) : super(exception: exception);
|
}) : super(exception: exception);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [taskId, ...super.props];
|
List<Object?> get props => [attachment, taskId, ...super.props];
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ class ViewAttachmentForWebSuccess extends DownloadAttachmentForWebSuccess {
|
|||||||
|
|
||||||
class ViewAttachmentForWebFailure extends DownloadAttachmentForWebFailure {
|
class ViewAttachmentForWebFailure extends DownloadAttachmentForWebFailure {
|
||||||
ViewAttachmentForWebFailure({
|
ViewAttachmentForWebFailure({
|
||||||
required super.attachmentBlobId,
|
required super.attachment,
|
||||||
super.taskId,
|
super.taskId,
|
||||||
super.exception,
|
super.exception,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class DownloadAttachmentForWebInteractor {
|
|||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
yield Left<Failure, Success>(
|
yield Left<Failure, Success>(
|
||||||
DownloadAttachmentForWebFailure(
|
DownloadAttachmentForWebFailure(
|
||||||
attachmentBlobId: attachment.blobId,
|
attachment: attachment,
|
||||||
taskId: taskId,
|
taskId: taskId,
|
||||||
exception: exception
|
exception: exception
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class ViewAttachmentForWebInteractor {
|
|||||||
(failure) {
|
(failure) {
|
||||||
if (failure is DownloadAttachmentForWebFailure) {
|
if (failure is DownloadAttachmentForWebFailure) {
|
||||||
return Left(ViewAttachmentForWebFailure(
|
return Left(ViewAttachmentForWebFailure(
|
||||||
attachmentBlobId: attachment.blobId,
|
attachment: attachment,
|
||||||
taskId: failure.taskId,
|
taskId: failure.taskId,
|
||||||
exception: failure.exception,
|
exception: failure.exception,
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
|||||||
import 'package:jmap_dart_client/jmap/mdn/disposition.dart';
|
import 'package:jmap_dart_client/jmap/mdn/disposition.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mdn/mdn.dart';
|
import 'package:jmap_dart_client/jmap/mdn/mdn.dart';
|
||||||
import 'package:mime/mime.dart';
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:model/email/eml_attachment.dart';
|
||||||
import 'package:model/model.dart';
|
import 'package:model/model.dart';
|
||||||
import 'package:permission_handler/permission_handler.dart';
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||||
@@ -28,6 +29,7 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
|
|||||||
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
|
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
|
||||||
|
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
|
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
|
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
|
||||||
import 'package:tmail_ui_user/features/email/domain/model/email_print.dart';
|
import 'package:tmail_ui_user/features/email/domain/model/email_print.dart';
|
||||||
@@ -810,7 +812,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
} else {
|
} else {
|
||||||
consumeState(Stream.value(
|
consumeState(Stream.value(
|
||||||
Left(DownloadAttachmentForWebFailure(
|
Left(DownloadAttachmentForWebFailure(
|
||||||
attachmentBlobId: attachment.blobId,
|
attachment: attachment,
|
||||||
exception: NotFoundSessionException()))
|
exception: NotFoundSessionException()))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -831,7 +833,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
} else {
|
} else {
|
||||||
consumeState(Stream.value(
|
consumeState(Stream.value(
|
||||||
Left(ViewAttachmentForWebFailure(
|
Left(ViewAttachmentForWebFailure(
|
||||||
attachmentBlobId: attachment.blobId,
|
attachment: attachment,
|
||||||
exception: NotFoundSessionException()))
|
exception: NotFoundSessionException()))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -876,12 +878,16 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
mailboxDashBoardController.deleteDownloadTask(failure.taskId!);
|
mailboxDashBoardController.deleteDownloadTask(failure.taskId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateAttachmentsViewState(failure.attachmentBlobId, Left(failure));
|
if (failure.attachment != null) {
|
||||||
|
_updateAttachmentsViewState(failure.attachment?.blobId, Left(failure));
|
||||||
|
}
|
||||||
|
|
||||||
if (currentOverlayContext != null && currentContext != null) {
|
if (currentOverlayContext != null && currentContext != null) {
|
||||||
appToast.showToastErrorMessage(
|
appToast.showToastErrorMessage(
|
||||||
currentOverlayContext!,
|
currentOverlayContext!,
|
||||||
AppLocalizations.of(currentContext!).attachment_download_failed);
|
failure.attachment is EMLAttachment
|
||||||
|
? AppLocalizations.of(currentContext!).downloadMessageAsEMLFailed
|
||||||
|
: AppLocalizations.of(currentContext!).attachment_download_failed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1141,6 +1147,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
case EmailActionType.printAll:
|
case EmailActionType.printAll:
|
||||||
_printEmail(context, presentationEmail);
|
_printEmail(context, presentationEmail);
|
||||||
break;
|
break;
|
||||||
|
case EmailActionType.downloadMessageAsEML:
|
||||||
|
_downloadMessageAsEML(presentationEmail);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -1772,4 +1781,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully);
|
AppLocalizations.of(currentContext!).eventReplyWasSentUnsuccessfully);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _downloadMessageAsEML(PresentationEmail presentationEmail) {
|
||||||
|
final emlAttachment = presentationEmail.createEMLAttachment();
|
||||||
|
if (emlAttachment.blobId == null) {
|
||||||
|
consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(exception: NotFoundEmailBlobIdException()))));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAttachmentForWeb(emlAttachment);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -473,6 +473,8 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
EmailActionType.unsubscribe,
|
EmailActionType.unsubscribe,
|
||||||
if (mailboxContain?.isArchive == false)
|
if (mailboxContain?.isArchive == false)
|
||||||
EmailActionType.archiveMessage,
|
EmailActionType.archiveMessage,
|
||||||
|
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
|
||||||
|
EmailActionType.downloadMessageAsEML
|
||||||
];
|
];
|
||||||
|
|
||||||
if (position == null) {
|
if (position == null) {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
|
||||||
import 'package:get/get_utils/src/get_utils/get_utils.dart';
|
import 'package:get/get_utils/src/get_utils/get_utils.dart';
|
||||||
import 'package:core/core.dart';
|
import 'package:core/core.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ extension EmailCacheExtension on EmailCache {
|
|||||||
: null,
|
: null,
|
||||||
headerCalendarEvent: headerCalendarEvent != null
|
headerCalendarEvent: headerCalendarEvent != null
|
||||||
? Map.fromIterables(headerCalendarEvent!.keys.map((value) => IndividualHeaderIdentifier(value)), headerCalendarEvent!.values)
|
? Map.fromIterables(headerCalendarEvent!.keys.map((value) => IndividualHeaderIdentifier(value)), headerCalendarEvent!.values)
|
||||||
: null
|
: null,
|
||||||
|
blobId: blobId != null ? Id(blobId!) : null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ extension EmailExtension on Email {
|
|||||||
replyTo: replyTo?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
replyTo: replyTo?.map((emailAddress) => emailAddress.toEmailAddressHiveCache()).toList(),
|
||||||
mailboxIds: mailboxIds?.toMapString(),
|
mailboxIds: mailboxIds?.toMapString(),
|
||||||
headerCalendarEvent: headerCalendarEvent?.toMapString(),
|
headerCalendarEvent: headerCalendarEvent?.toMapString(),
|
||||||
|
blobId: blobId?.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
@HiveField(14)
|
@HiveField(14)
|
||||||
Map<String, String?>? headerCalendarEvent;
|
Map<String, String?>? headerCalendarEvent;
|
||||||
|
|
||||||
|
@HiveField(15)
|
||||||
|
final String? blobId;
|
||||||
|
|
||||||
EmailCache(
|
EmailCache(
|
||||||
this.id,
|
this.id,
|
||||||
{
|
{
|
||||||
@@ -71,6 +74,7 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
this.replyTo,
|
this.replyTo,
|
||||||
this.mailboxIds,
|
this.mailboxIds,
|
||||||
this.headerCalendarEvent,
|
this.headerCalendarEvent,
|
||||||
|
this.blobId,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -91,5 +95,6 @@ class EmailCache extends HiveObject with EquatableMixin {
|
|||||||
hasAttachment,
|
hasAttachment,
|
||||||
mailboxIds,
|
mailboxIds,
|
||||||
headerCalendarEvent,
|
headerCalendarEvent,
|
||||||
|
blobId,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ class ThreadConstants {
|
|||||||
static final defaultLimit = UnsignedInt(maxCountEmails);
|
static final defaultLimit = UnsignedInt(maxCountEmails);
|
||||||
static final propertiesDefault = Properties({
|
static final propertiesDefault = Properties({
|
||||||
EmailProperty.id,
|
EmailProperty.id,
|
||||||
|
EmailProperty.blobId,
|
||||||
EmailProperty.subject,
|
EmailProperty.subject,
|
||||||
EmailProperty.from,
|
EmailProperty.from,
|
||||||
EmailProperty.to,
|
EmailProperty.to,
|
||||||
@@ -29,6 +30,7 @@ class ThreadConstants {
|
|||||||
|
|
||||||
static final propertiesQuickSearch = Properties({
|
static final propertiesQuickSearch = Properties({
|
||||||
EmailProperty.id,
|
EmailProperty.id,
|
||||||
|
EmailProperty.blobId,
|
||||||
EmailProperty.subject,
|
EmailProperty.subject,
|
||||||
EmailProperty.from,
|
EmailProperty.from,
|
||||||
EmailProperty.to,
|
EmailProperty.to,
|
||||||
@@ -53,6 +55,7 @@ class ThreadConstants {
|
|||||||
|
|
||||||
static final propertiesGetDetailedEmail = Properties({
|
static final propertiesGetDetailedEmail = Properties({
|
||||||
EmailProperty.id,
|
EmailProperty.id,
|
||||||
|
EmailProperty.blobId,
|
||||||
EmailProperty.subject,
|
EmailProperty.subject,
|
||||||
EmailProperty.from,
|
EmailProperty.from,
|
||||||
EmailProperty.to,
|
EmailProperty.to,
|
||||||
@@ -74,6 +77,7 @@ class ThreadConstants {
|
|||||||
|
|
||||||
static final propertiesCalendarEvent = Properties({
|
static final propertiesCalendarEvent = Properties({
|
||||||
EmailProperty.id,
|
EmailProperty.id,
|
||||||
|
EmailProperty.blobId,
|
||||||
EmailProperty.subject,
|
EmailProperty.subject,
|
||||||
EmailProperty.from,
|
EmailProperty.from,
|
||||||
EmailProperty.to,
|
EmailProperty.to,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"@@last_modified": "2024-04-19T16:31:35.757887",
|
"@@last_modified": "2024-05-16T11:36:53.008027",
|
||||||
"initializing_data": "Initializing data...",
|
"initializing_data": "Initializing data...",
|
||||||
"@initializing_data": {
|
"@initializing_data": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -3719,5 +3719,17 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"downloadMessageAsEML": "Download message as EML",
|
||||||
|
"@downloadMessageAsEML": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
|
"downloadMessageAsEMLFailed": "Download message as EML failed",
|
||||||
|
"@downloadMessageAsEMLFailed": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3879,4 +3879,18 @@ class AppLocalizations {
|
|||||||
name: 'youMayAttendThisMeeting',
|
name: 'youMayAttendThisMeeting',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get downloadMessageAsEML {
|
||||||
|
return Intl.message(
|
||||||
|
'Download message as EML',
|
||||||
|
name: 'downloadMessageAsEML',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get downloadMessageAsEMLFailed {
|
||||||
|
return Intl.message(
|
||||||
|
'Download message as EML failed',
|
||||||
|
name: 'downloadMessageAsEMLFailed',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -44,8 +44,8 @@ class Attachment with EquatableMixin {
|
|||||||
final downloadUri = downloadUriTemplate.expand({
|
final downloadUri = downloadUriTemplate.expand({
|
||||||
'accountId' : accountId.id.value,
|
'accountId' : accountId.id.value,
|
||||||
'blobId' : '${blobId?.value}',
|
'blobId' : '${blobId?.value}',
|
||||||
'name' : '$name',
|
'name' : name ?? '',
|
||||||
'type' : '${type?.mimeType}',
|
'type' : type?.mimeType ?? '',
|
||||||
});
|
});
|
||||||
return Uri.decodeFull(downloadUri);
|
return Uri.decodeFull(downloadUri);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,5 +27,6 @@ enum EmailActionType {
|
|||||||
unsubscribe,
|
unsubscribe,
|
||||||
composeFromUnsubscribeMailtoLink,
|
composeFromUnsubscribeMailtoLink,
|
||||||
archiveMessage,
|
archiveMessage,
|
||||||
printAll
|
printAll,
|
||||||
|
downloadMessageAsEML
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
class EmailProperty {
|
class EmailProperty {
|
||||||
static const String id = 'id';
|
static const String id = 'id';
|
||||||
|
static const String blobId = 'blobId';
|
||||||
static const String keywords = 'keywords';
|
static const String keywords = 'keywords';
|
||||||
static const String size = 'size';
|
static const String size = 'size';
|
||||||
static const String receivedAt = 'receivedAt';
|
static const String receivedAt = 'receivedAt';
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
import 'package:model/email/attachment.dart';
|
||||||
|
|
||||||
|
class EMLAttachment extends Attachment {
|
||||||
|
|
||||||
|
EMLAttachment({
|
||||||
|
super.partId,
|
||||||
|
super.blobId,
|
||||||
|
super.size,
|
||||||
|
super.name,
|
||||||
|
super.type,
|
||||||
|
super.cid,
|
||||||
|
super.disposition,
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
|
|
||||||
import 'package:core/presentation/extensions/string_extension.dart';
|
import 'package:core/presentation/extensions/string_extension.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
|
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
|
||||||
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
|
import 'package:jmap_dart_client/jmap/core/utc_date.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||||
@@ -21,6 +22,7 @@ import 'package:model/mailbox/select_mode.dart';
|
|||||||
class PresentationEmail with EquatableMixin {
|
class PresentationEmail with EquatableMixin {
|
||||||
|
|
||||||
final EmailId? id;
|
final EmailId? id;
|
||||||
|
final Id? blobId;
|
||||||
final Map<KeyWordIdentifier, bool>? keywords;
|
final Map<KeyWordIdentifier, bool>? keywords;
|
||||||
final UnsignedInt? size;
|
final UnsignedInt? size;
|
||||||
final UTCDate? receivedAt;
|
final UTCDate? receivedAt;
|
||||||
@@ -44,6 +46,7 @@ class PresentationEmail with EquatableMixin {
|
|||||||
|
|
||||||
PresentationEmail({
|
PresentationEmail({
|
||||||
this.id,
|
this.id,
|
||||||
|
this.blobId,
|
||||||
this.keywords,
|
this.keywords,
|
||||||
this.size,
|
this.size,
|
||||||
this.receivedAt,
|
this.receivedAt,
|
||||||
@@ -142,6 +145,7 @@ class PresentationEmail with EquatableMixin {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
id,
|
id,
|
||||||
|
blobId,
|
||||||
keywords,
|
keywords,
|
||||||
size,
|
size,
|
||||||
receivedAt,
|
receivedAt,
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ extension EmailExtension on Email {
|
|||||||
Email updatedEmail({Map<KeyWordIdentifier, bool>? newKeywords, Map<MailboxId, bool>? newMailboxIds}) {
|
Email updatedEmail({Map<KeyWordIdentifier, bool>? newKeywords, Map<MailboxId, bool>? newMailboxIds}) {
|
||||||
return Email(
|
return Email(
|
||||||
id: id,
|
id: id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: newKeywords ?? keywords,
|
keywords: newKeywords ?? keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -91,6 +92,7 @@ extension EmailExtension on Email {
|
|||||||
PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE}) {
|
PresentationEmail toPresentationEmail({SelectMode selectMode = SelectMode.INACTIVE}) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: id,
|
id: id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -113,6 +115,7 @@ extension EmailExtension on Email {
|
|||||||
Email combineEmail(Email newEmail, Properties updatedProperties) {
|
Email combineEmail(Email newEmail, Properties updatedProperties) {
|
||||||
return Email(
|
return Email(
|
||||||
id: newEmail.id,
|
id: newEmail.id,
|
||||||
|
blobId: updatedProperties.contain(EmailProperty.blobId) ? newEmail.blobId : blobId,
|
||||||
keywords: updatedProperties.contain(EmailProperty.keywords) ? newEmail.keywords : keywords,
|
keywords: updatedProperties.contain(EmailProperty.keywords) ? newEmail.keywords : keywords,
|
||||||
size: updatedProperties.contain(EmailProperty.size) ? newEmail.size : size,
|
size: updatedProperties.contain(EmailProperty.size) ? newEmail.size : size,
|
||||||
receivedAt: updatedProperties.contain(EmailProperty.receivedAt) ? newEmail.receivedAt : receivedAt,
|
receivedAt: updatedProperties.contain(EmailProperty.receivedAt) ? newEmail.receivedAt : receivedAt,
|
||||||
@@ -171,6 +174,7 @@ extension EmailExtension on Email {
|
|||||||
) {
|
) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: emailId ?? id,
|
id: emailId ?? id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:core/data/constants/constant.dart';
|
||||||
import 'package:core/domain/extensions/datetime_extension.dart';
|
import 'package:core/domain/extensions/datetime_extension.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.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.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||||
import 'package:jmap_dart_client/jmap/mail/email/keyword_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:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||||
import 'package:model/email/email_action_type.dart';
|
import 'package:model/email/email_action_type.dart';
|
||||||
|
import 'package:model/email/eml_attachment.dart';
|
||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:model/extensions/email_address_extension.dart';
|
import 'package:model/extensions/email_address_extension.dart';
|
||||||
import 'package:model/extensions/list_email_address_extension.dart';
|
import 'package:model/extensions/list_email_address_extension.dart';
|
||||||
@@ -44,6 +47,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
PresentationEmail toggleSelect() {
|
PresentationEmail toggleSelect() {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -67,6 +71,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
PresentationEmail toSelectedEmail({required SelectMode selectMode}) {
|
PresentationEmail toSelectedEmail({required SelectMode selectMode}) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -90,6 +95,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
Email toEmail() {
|
Email toEmail() {
|
||||||
return Email(
|
return Email(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -146,6 +152,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
|
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -182,6 +189,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
PresentationEmail withRouteWeb(Uri routeWeb) {
|
PresentationEmail withRouteWeb(Uri routeWeb) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -205,6 +213,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
PresentationEmail updateKeywords(Map<KeyWordIdentifier, bool>? newKeywords) {
|
PresentationEmail updateKeywords(Map<KeyWordIdentifier, bool>? newKeywords) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: newKeywords,
|
keywords: newKeywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -228,6 +237,7 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
PresentationEmail syncPresentationEmail({PresentationMailbox? mailboxContain, Uri? routeWeb}) {
|
PresentationEmail syncPresentationEmail({PresentationMailbox? mailboxContain, Uri? routeWeb}) {
|
||||||
return PresentationEmail(
|
return PresentationEmail(
|
||||||
id: this.id,
|
id: this.id,
|
||||||
|
blobId: blobId,
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
size: size,
|
size: size,
|
||||||
receivedAt: receivedAt,
|
receivedAt: receivedAt,
|
||||||
@@ -262,4 +272,12 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EMLAttachment createEMLAttachment() {
|
||||||
|
return EMLAttachment(
|
||||||
|
blobId: blobId,
|
||||||
|
name: getEmailTitle().isEmpty ? '${blobId?.value}.eml' : '${getEmailTitle()}.eml',
|
||||||
|
type: MediaType.parse(Constant.octetStreamMimeType)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -55,7 +55,7 @@ void main() {
|
|||||||
(_) => Stream.value(
|
(_) => Stream.value(
|
||||||
Left(
|
Left(
|
||||||
DownloadAttachmentForWebFailure(
|
DownloadAttachmentForWebFailure(
|
||||||
attachmentBlobId: testAttachment.blobId,
|
attachment: testAttachment,
|
||||||
taskId: testDownloadTaskId,
|
taskId: testDownloadTaskId,
|
||||||
exception: testException,
|
exception: testException,
|
||||||
),
|
),
|
||||||
@@ -75,7 +75,7 @@ void main() {
|
|||||||
emitsInOrder([
|
emitsInOrder([
|
||||||
Left(
|
Left(
|
||||||
ViewAttachmentForWebFailure(
|
ViewAttachmentForWebFailure(
|
||||||
attachmentBlobId: testAttachment.blobId,
|
attachment: testAttachment,
|
||||||
taskId: testDownloadTaskId,
|
taskId: testDownloadTaskId,
|
||||||
exception: testException,
|
exception: testException,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ void main() {
|
|||||||
};
|
};
|
||||||
final baseOption = BaseOptions(headers: headers);
|
final baseOption = BaseOptions(headers: headers);
|
||||||
dio = Dio(baseOption)
|
dio = Dio(baseOption)
|
||||||
..options.baseUrl = baseUrl;;
|
..options.baseUrl = baseUrl;
|
||||||
|
|
||||||
authenticationClient = MockAuthenticationClientBase();
|
authenticationClient = MockAuthenticationClientBase();
|
||||||
tokenOidcCacheManager = MockTokenOidcCacheManager();
|
tokenOidcCacheManager = MockTokenOidcCacheManager();
|
||||||
|
|||||||
Reference in New Issue
Block a user