TF-1961 Transform html email content in composer
(cherry picked from commit f5470c130744242195b265a0ea4aad044ad8e740)
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
/// Transforms plain text messages.
|
||||
abstract class TextTransformer {
|
||||
const TextTransformer();
|
||||
|
||||
String process(String text);
|
||||
String process(String text, HtmlEscape htmlEscape);
|
||||
}
|
||||
@@ -12,7 +12,9 @@ import 'package:html/dom.dart';
|
||||
|
||||
class ImageTransformer extends DomTransformer {
|
||||
|
||||
const ImageTransformer();
|
||||
final bool useLoadingAttribute;
|
||||
|
||||
const ImageTransformer({this.useLoadingAttribute = false});
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
@@ -40,14 +42,19 @@ class ImageTransformer extends DomTransformer {
|
||||
);
|
||||
imageElement.attributes['src'] = imageBase64 ?? src;
|
||||
} else if (src.startsWith('https://') || src.startsWith('http://')) {
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
imageElement.attributes['class'] = '$classAttribute lazy-loading';
|
||||
if (useLoadingAttribute) {
|
||||
imageElement.attributes['loading'] = 'lazy';
|
||||
} else {
|
||||
imageElement.attributes['class'] = 'lazy-loading';
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
imageElement.attributes['class'] = '$classAttribute lazy-loading';
|
||||
} else {
|
||||
imageElement.attributes['class'] = 'lazy-loading';
|
||||
}
|
||||
imageElement.attributes['data-src'] = src;
|
||||
imageElement.attributes.remove('src');
|
||||
imageElement.attributes.remove('loading');
|
||||
}
|
||||
imageElement.attributes['data-src'] = src;
|
||||
imageElement.attributes.remove('src');
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/html_template.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
class RemoveTooltipLinkTransformer extends DomTransformer {
|
||||
|
||||
const RemoveTooltipLinkTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}) async {
|
||||
final linkElements = document.querySelectorAll('a.$nameClassToolTip');
|
||||
await Future.wait(linkElements.map((linkElement) async {
|
||||
final classAttribute = linkElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
final newClassAttribute = classAttribute.replaceFirst(nameClassToolTip, '');
|
||||
linkElement.attributes['class'] = newClassAttribute;
|
||||
}
|
||||
final listSpanTag = linkElement.querySelectorAll('span.tooltiptext');
|
||||
log('RemoveTooltipLinkTransformer::process:listSpanTag: ${listSpanTag.length}');
|
||||
if (listSpanTag.isNotEmpty) {
|
||||
for (var element in listSpanTag) {
|
||||
element.remove();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:html/dom.dart';
|
||||
|
||||
class ReplaceLazyLoadImageTransformer extends DomTransformer {
|
||||
|
||||
const ReplaceLazyLoadImageTransformer();
|
||||
|
||||
@override
|
||||
Future<void> process({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
DioClient? dioClient
|
||||
}) async {
|
||||
final imageElements = document.querySelectorAll('img.lazy-loading');
|
||||
await Future.wait(imageElements.map((imageElement) async {
|
||||
final classAttribute = imageElement.attributes['class'];
|
||||
if (classAttribute != null) {
|
||||
final newClassAttribute = classAttribute.replaceFirst('lazy-loading', '');
|
||||
imageElement.attributes['class'] = newClassAttribute;
|
||||
}
|
||||
final dataSrc = imageElement.attributes['data-src'];
|
||||
if (dataSrc != null) {
|
||||
imageElement.attributes['src'] = dataSrc;
|
||||
imageElement.attributes.remove('data-src');
|
||||
}
|
||||
imageElement.attributes['loading'] = 'lazy';
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/message_content_transformer.dart';
|
||||
|
||||
class HtmlTransform {
|
||||
|
||||
final DioClient _dioClient;
|
||||
final HtmlEscape _htmlEscape;
|
||||
|
||||
HtmlTransform(this._dioClient);
|
||||
HtmlTransform(this._dioClient, this._htmlEscape);
|
||||
|
||||
/// Transforms this message to HTML code.
|
||||
Future<String> transformToHtml({
|
||||
required String contentHtml,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
required String htmlContent,
|
||||
Map<String, String>? mapCidImageDownloadUrl,
|
||||
TransformConfiguration? transformConfiguration,
|
||||
}) async {
|
||||
transformConfiguration ??= TransformConfiguration.create();
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient);
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient, _htmlEscape);
|
||||
final document = await transformer.toDocument(
|
||||
message: contentHtml,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID
|
||||
message: htmlContent,
|
||||
mapUrlDownloadCID: mapCidImageDownloadUrl
|
||||
);
|
||||
return document.outerHtml;
|
||||
}
|
||||
@@ -28,7 +31,7 @@ class HtmlTransform {
|
||||
TransformConfiguration? transformConfiguration
|
||||
}) {
|
||||
transformConfiguration ??= TransformConfiguration.create();
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient);
|
||||
final transformer = MessageContentTransformer(transformConfiguration, _dioClient, _htmlEscape);
|
||||
final message = transformer.toMessage(content);
|
||||
return message;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:html/dom.dart';
|
||||
@@ -5,23 +7,28 @@ import 'package:html/parser.dart' show parse;
|
||||
|
||||
/// Transforms messages
|
||||
class MessageContentTransformer {
|
||||
/// The configuration used for the transformation
|
||||
final TransformConfiguration configuration;
|
||||
final DioClient dioClient;
|
||||
/// The _configuration used for the transformation
|
||||
final TransformConfiguration _configuration;
|
||||
final DioClient _dioClient;
|
||||
final HtmlEscape _htmlEscape;
|
||||
|
||||
MessageContentTransformer(this.configuration, this.dioClient);
|
||||
MessageContentTransformer(
|
||||
this._configuration,
|
||||
this._dioClient,
|
||||
this._htmlEscape
|
||||
);
|
||||
|
||||
Future<void> _transformDocument({
|
||||
required Document document,
|
||||
Map<String, String>? mapUrlDownloadCID
|
||||
}) async {
|
||||
await Future.wait([
|
||||
if (configuration.domTransformers.isNotEmpty)
|
||||
...configuration.domTransformers.map((domTransformer) async =>
|
||||
if (_configuration.domTransformers.isNotEmpty)
|
||||
..._configuration.domTransformers.map((domTransformer) async =>
|
||||
domTransformer.process(
|
||||
document: document,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID,
|
||||
dioClient: dioClient
|
||||
dioClient: _dioClient
|
||||
)
|
||||
)
|
||||
]);
|
||||
@@ -40,9 +47,9 @@ class MessageContentTransformer {
|
||||
}
|
||||
|
||||
String _transformMessage(String message) {
|
||||
if (configuration.textTransformers.isNotEmpty) {
|
||||
for (var transformer in configuration.textTransformers) {
|
||||
message = transformer.process(message);
|
||||
if (_configuration.textTransformers.isNotEmpty) {
|
||||
for (var transformer in _configuration.textTransformers) {
|
||||
message = transformer.process(message, _htmlEscape);
|
||||
}
|
||||
}
|
||||
return message;
|
||||
|
||||
+2
-4
@@ -6,10 +6,8 @@ import 'package:core/presentation/utils/html_transformer/base/text_transformer.d
|
||||
|
||||
class SanitizeAutolinkHtmlTransformers extends TextTransformer {
|
||||
|
||||
final HtmlEscape htmlEscape;
|
||||
|
||||
SanitizeAutolinkHtmlTransformers(this.htmlEscape);
|
||||
const SanitizeAutolinkHtmlTransformers();
|
||||
|
||||
@override
|
||||
String process(String text) => SanitizeAutolinkFilter(htmlEscape).process(text);
|
||||
String process(String text, HtmlEscape htmlEscape) => SanitizeAutolinkFilter(htmlEscape).process(text);
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/base/text_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/add_target_blank_in_tag_a_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockcode_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/blockquoted_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/remove_tooltip_link_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/replace_lazy_load_image_transformer.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/sigature_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart';
|
||||
|
||||
/// Contains the configuration for all transformations.
|
||||
class TransformConfiguration {
|
||||
@@ -25,6 +29,45 @@ class TransformConfiguration {
|
||||
this.textTransformers
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forReplyForwardEmail() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const ReplaceLazyLoadImageTransformer(),
|
||||
if (PlatformInfo.isWeb)
|
||||
const RemoveTooltipLinkTransformer(),
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(useLoadingAttribute: true),
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forComposeEmailPlatformWeb() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(useLoadingAttribute: true)
|
||||
]
|
||||
);
|
||||
|
||||
factory TransformConfiguration.forPreviewEmailPlatformWeb() => TransformConfiguration.create(
|
||||
customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
const BlockCodeTransformer(),
|
||||
const AddTargetBlankInTagATransformer(),
|
||||
const ImageTransformer(),
|
||||
const AddTooltipLinkTransformer(),
|
||||
]
|
||||
);
|
||||
|
||||
/// Provides easy access to a standard configuration that does not block external images.
|
||||
static const TransformConfiguration standardConfiguration = TransformConfiguration(
|
||||
standardDomTransformers,
|
||||
@@ -61,13 +104,7 @@ class TransformConfiguration {
|
||||
ImageTransformer(),
|
||||
];
|
||||
|
||||
static const List<DomTransformer> domTransformersForDraftEmail = [
|
||||
RemoveScriptTransformer(),
|
||||
BlockQuotedTransformer(),
|
||||
BlockCodeTransformer(),
|
||||
AddTargetBlankInTagATransformer(),
|
||||
ImageTransformer(),
|
||||
static const List<TextTransformer> standardTextTransformers = [
|
||||
SanitizeAutolinkHtmlTransformers()
|
||||
];
|
||||
|
||||
static const List<TextTransformer> standardTextTransformers = [];
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import 'package:tmail_ui_user/features/session/domain/usecases/get_session_inter
|
||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||
|
||||
abstract class ReloadableController extends BaseController {
|
||||
final DynamicUrlInterceptors _dynamicUrlInterceptors = Get.find<DynamicUrlInterceptors>();
|
||||
final DynamicUrlInterceptors dynamicUrlInterceptors = Get.find<DynamicUrlInterceptors>();
|
||||
final GetSessionInteractor _getSessionInteractor = Get.find<GetSessionInteractor>();
|
||||
final GetAuthenticatedAccountInteractor _getAuthenticatedAccountInteractor;
|
||||
final UpdateAuthenticationAccountInteractor _updateAuthenticationAccountInteractor;
|
||||
@@ -72,8 +72,8 @@ abstract class ReloadableController extends BaseController {
|
||||
}
|
||||
|
||||
void _setUpInterceptors(GetCredentialViewState credentialViewState) {
|
||||
_dynamicUrlInterceptors.setJmapUrl(credentialViewState.baseUrl.origin);
|
||||
_dynamicUrlInterceptors.changeBaseUrl(credentialViewState.baseUrl.origin);
|
||||
dynamicUrlInterceptors.setJmapUrl(credentialViewState.baseUrl.origin);
|
||||
dynamicUrlInterceptors.changeBaseUrl(credentialViewState.baseUrl.origin);
|
||||
authorizationInterceptors.setBasicAuthorization(
|
||||
credentialViewState.userName.value,
|
||||
credentialViewState.password.value,
|
||||
@@ -100,10 +100,10 @@ abstract class ReloadableController extends BaseController {
|
||||
void _handleGetSessionSuccess(GetSessionSuccess success) {
|
||||
final session = success.session;
|
||||
final personalAccount = session.personalAccount;
|
||||
final apiUrl = session.getQualifiedApiUrl(baseUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl);
|
||||
log('ReloadableController::_handleGetSessionSuccess():apiUrl: $apiUrl');
|
||||
if (apiUrl.isNotEmpty) {
|
||||
_dynamicUrlInterceptors.changeBaseUrl(apiUrl);
|
||||
dynamicUrlInterceptors.changeBaseUrl(apiUrl);
|
||||
updateAuthenticationAccount(session, personalAccount.accountId, session.username);
|
||||
handleReloaded(session);
|
||||
} else {
|
||||
@@ -120,8 +120,8 @@ abstract class ReloadableController extends BaseController {
|
||||
}
|
||||
|
||||
void _setUpInterceptorsOidc(GetStoredTokenOidcSuccess tokenOidcSuccess) {
|
||||
_dynamicUrlInterceptors.setJmapUrl(tokenOidcSuccess.baseUrl.toString());
|
||||
_dynamicUrlInterceptors.changeBaseUrl(tokenOidcSuccess.baseUrl.toString());
|
||||
dynamicUrlInterceptors.setJmapUrl(tokenOidcSuccess.baseUrl.toString());
|
||||
dynamicUrlInterceptors.changeBaseUrl(tokenOidcSuccess.baseUrl.toString());
|
||||
authorizationInterceptors.setTokenAndAuthorityOidc(
|
||||
newToken: tokenOidcSuccess.tokenOidc.toToken(),
|
||||
newConfig: tokenOidcSuccess.oidcConfiguration);
|
||||
@@ -140,7 +140,7 @@ abstract class ReloadableController extends BaseController {
|
||||
}
|
||||
|
||||
void updateAuthenticationAccount(Session session, AccountId accountId, UserName userName) {
|
||||
final apiUrl = session.getQualifiedApiUrl(baseUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
final apiUrl = session.getQualifiedApiUrl(baseUrl: dynamicUrlInterceptors.jmapUrl);
|
||||
log('ReloadableController::updateAuthenticationAccount():apiUrl: $apiUrl');
|
||||
if (apiUrl.isNotEmpty) {
|
||||
consumeState(_updateAuthenticationAccountInteractor.execute(accountId, apiUrl, userName));
|
||||
|
||||
@@ -4,17 +4,35 @@ import 'package:tmail_ui_user/features/base/styles/cupertino_loading_widget_styl
|
||||
class CupertinoLoadingWidget extends StatelessWidget {
|
||||
|
||||
final double? size;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
const CupertinoLoadingWidget({super.key, this.size});
|
||||
const CupertinoLoadingWidget({super.key, this.size, this.padding});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
height: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
child: const CupertinoActivityIndicator(color: CupertinoLoadingWidgetStyles.progressColor)
|
||||
)
|
||||
);
|
||||
if (padding != null) {
|
||||
return Padding(
|
||||
padding: padding!,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
height: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
child: const CupertinoActivityIndicator(
|
||||
color: CupertinoLoadingWidgetStyles.progressColor
|
||||
)
|
||||
)
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
width: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
height: size ?? CupertinoLoadingWidgetStyles.size,
|
||||
child: const CupertinoActivityIndicator(
|
||||
color: CupertinoLoadingWidgetStyles.progressColor
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:core/presentation/utils/app_toast.dart';
|
||||
import 'package:core/presentation/utils/responsive_utils.dart';
|
||||
import 'package:core/presentation/utils/style_utils.dart';
|
||||
import 'package:core/presentation/views/button/icon_button_web.dart';
|
||||
import 'package:core/presentation/views/button/tmail_button_widget.dart';
|
||||
import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.dart';
|
||||
import 'package:core/presentation/views/text/text_field_builder.dart';
|
||||
import 'package:core/presentation/views/text/text_overflow_builder.dart';
|
||||
@@ -321,22 +322,17 @@ abstract class BaseComposerView extends GetWidget<ComposerController>
|
||||
)
|
||||
),
|
||||
const Spacer(),
|
||||
Material(
|
||||
type: MaterialType.circle,
|
||||
color: Colors.transparent,
|
||||
child: TextButton(
|
||||
onPressed: controller.toggleDisplayAttachments,
|
||||
child: Text(
|
||||
expandModeAttachment == ExpandMode.EXPAND
|
||||
? AppLocalizations.of(context).hide
|
||||
: '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})',
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
color: AppColor.colorTextButton
|
||||
)
|
||||
)
|
||||
)
|
||||
TMailButtonWidget.fromText(
|
||||
text: expandModeAttachment == ExpandMode.EXPAND
|
||||
? AppLocalizations.of(context).hide
|
||||
: '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})',
|
||||
onTapActionCallback: controller.toggleDisplayAttachments,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 12,
|
||||
color: AppColor.colorTextButton
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
)
|
||||
],
|
||||
);
|
||||
|
||||
@@ -29,6 +29,7 @@ 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/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/mailbox/data/datasource/mailbox_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart';
|
||||
@@ -163,6 +164,7 @@ class ComposerBindings extends BaseBindings {
|
||||
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
|
||||
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
|
||||
Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find<ComposerRepository>()));
|
||||
Get.lazyPut(() => TransformHtmlEmailContentInteractor(Get.find<EmailRepository>()));
|
||||
|
||||
IdentityInteractorsBindings().dependencies();
|
||||
}
|
||||
@@ -184,6 +186,7 @@ class ComposerBindings extends BaseBindings {
|
||||
Get.find<SaveComposerCacheOnWebInteractor>(),
|
||||
Get.find<RichTextWebController>(),
|
||||
Get.find<DownloadImageAsBase64Interactor>(),
|
||||
Get.find<TransformHtmlEmailContentInteractor>(),
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,11 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/email_ac
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
|
||||
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';
|
||||
@@ -60,6 +63,7 @@ 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';
|
||||
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_action_type.dart';
|
||||
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
|
||||
@@ -95,7 +99,7 @@ class ComposerController extends BaseController {
|
||||
final bccAddressExpandMode = ExpandMode.EXPAND.obs;
|
||||
final identitySelected = Rxn<Identity>();
|
||||
final listIdentities = <Identity>[].obs;
|
||||
final emailContentsViewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
|
||||
final emailContentsViewState = Rxn<Either<Failure, Success>>();
|
||||
final hasRequestReadReceipt = false.obs;
|
||||
|
||||
final LocalFilePickerInteractor _localFilePickerInteractor;
|
||||
@@ -109,6 +113,7 @@ class ComposerController extends BaseController {
|
||||
final SaveComposerCacheOnWebInteractor _saveComposerCacheOnWebInteractor;
|
||||
final RichTextWebController richTextWebController;
|
||||
final DownloadImageAsBase64Interactor _downloadImageAsBase64Interactor;
|
||||
final TransformHtmlEmailContentInteractor _transformHtmlEmailContentInteractor;
|
||||
|
||||
GetAutoCompleteWithDeviceContactInteractor? _getAutoCompleteWithDeviceContactInteractor;
|
||||
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
|
||||
@@ -145,7 +150,6 @@ class ComposerController extends BaseController {
|
||||
List<Attachment> initialAttachments = <Attachment>[];
|
||||
String? _textEditorWeb;
|
||||
String? _initTextEditor;
|
||||
String? _emailContents;
|
||||
double? maxWithEditor;
|
||||
late Worker uploadInlineImageWorker;
|
||||
|
||||
@@ -201,6 +205,7 @@ class ComposerController extends BaseController {
|
||||
this._saveComposerCacheOnWebInteractor,
|
||||
this.richTextWebController,
|
||||
this._downloadImageAsBase64Interactor,
|
||||
this._transformHtmlEmailContentInteractor,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -264,7 +269,9 @@ class ComposerController extends BaseController {
|
||||
@override
|
||||
void handleSuccessViewState(Success success) {
|
||||
super.handleSuccessViewState(success);
|
||||
if (success is GetEmailContentLoading) {
|
||||
if (success is GetEmailContentLoading ||
|
||||
success is TransformHtmlEmailContentLoading ||
|
||||
success is TransformHtmlEmailContentSuccess) {
|
||||
emailContentsViewState.value = Right(success);
|
||||
} else if (success is LocalFilePickerSuccess) {
|
||||
_pickFileSuccess(success);
|
||||
@@ -299,7 +306,8 @@ class ComposerController extends BaseController {
|
||||
super.handleFailureViewState(failure);
|
||||
if (failure is LocalFilePickerFailure || failure is LocalFilePickerCancel) {
|
||||
_pickFileFailure(failure);
|
||||
} else if (failure is GetEmailContentFailure) {
|
||||
} else if (failure is GetEmailContentFailure ||
|
||||
failure is TransformHtmlEmailContentFailure) {
|
||||
emailContentsViewState.value = Left(failure);
|
||||
}
|
||||
}
|
||||
@@ -387,39 +395,105 @@ class ComposerController extends BaseController {
|
||||
: Get.arguments;
|
||||
if (arguments is ComposerArguments) {
|
||||
composerArguments.value = arguments;
|
||||
|
||||
injectAutoCompleteBindings(
|
||||
mailboxDashBoardController.sessionCurrent,
|
||||
mailboxDashBoardController.accountId.value);
|
||||
mailboxDashBoardController.sessionCurrent,
|
||||
mailboxDashBoardController.accountId.value
|
||||
);
|
||||
|
||||
if (arguments.emailActionType == EmailActionType.edit) {
|
||||
_getEmailContentAction(arguments);
|
||||
switch(arguments.emailActionType) {
|
||||
case EmailActionType.editDraft:
|
||||
_initEmailAddress(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: EmailActionType.editDraft
|
||||
);
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: EmailActionType.editDraft
|
||||
);
|
||||
_getEmailContentFromEmailId(
|
||||
emailId: arguments.presentationEmail!.id!,
|
||||
isDraftEmail: arguments.presentationEmail!.isDraft
|
||||
);
|
||||
break;
|
||||
case EmailActionType.editSendingEmail:
|
||||
_initEmailAddress(
|
||||
presentationEmail: arguments.sendingEmail!.presentationEmail,
|
||||
actionType: EmailActionType.editSendingEmail
|
||||
);
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.sendingEmail!.presentationEmail,
|
||||
actionType: EmailActionType.editSendingEmail
|
||||
);
|
||||
_getEmailContentFromSendingEmail(arguments.sendingEmail!);
|
||||
break;
|
||||
case EmailActionType.composeFromContentShared:
|
||||
_getEmailContentFromContentShared(arguments.emailContents!);
|
||||
break;
|
||||
case EmailActionType.composeFromFileShared:
|
||||
_addAttachmentFromFileShare(arguments.listSharedMediaFile!);
|
||||
break;
|
||||
case EmailActionType.composeFromEmailAddress:
|
||||
listToEmailAddress.add(arguments.emailAddress!);
|
||||
isInitialRecipient.value = true;
|
||||
toAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
_updateStatusEmailSendButton();
|
||||
break;
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
_initEmailAddress(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: arguments.emailActionType,
|
||||
mailboxRole: arguments.presentationEmail!.mailboxContain?.role ?? mailboxDashBoardController.selectedMailbox.value?.role
|
||||
);
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: arguments.emailActionType
|
||||
);
|
||||
_transformHtmlEmailContent(arguments.emailContents);
|
||||
break;
|
||||
case EmailActionType.forward:
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: arguments.emailActionType
|
||||
);
|
||||
_initAttachments(arguments.attachments ?? []);
|
||||
_transformHtmlEmailContent(arguments.emailContents);
|
||||
break;
|
||||
case EmailActionType.reopenComposerBrowser:
|
||||
_initEmailAddress(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: EmailActionType.reopenComposerBrowser
|
||||
);
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: EmailActionType.reopenComposerBrowser
|
||||
);
|
||||
_initAttachments(arguments.attachments ?? []);
|
||||
_getEmailContentFromSessionStorageBrowser(arguments.emailContents!);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_initEmailAddress(arguments);
|
||||
_initSubjectEmail(arguments);
|
||||
_initAttachments(arguments);
|
||||
}
|
||||
|
||||
_autoFocusFieldWhenLauncher();
|
||||
}
|
||||
|
||||
void _initSubjectEmail(ComposerArguments arguments) {
|
||||
if (currentContext != null) {
|
||||
final subjectEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
|
||||
final newSubject = arguments.emailActionType.getSubjectComposer(currentContext!, subjectEmail);
|
||||
setSubjectEmail(newSubject);
|
||||
subjectEmailInputController.text = newSubject;
|
||||
}
|
||||
void _initSubjectEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required EmailActionType actionType
|
||||
}) {
|
||||
final subjectEmail = presentationEmail.getEmailTitle().trim();
|
||||
final newSubject = actionType.getSubjectComposer(currentContext, subjectEmail);
|
||||
setSubjectEmail(newSubject);
|
||||
subjectEmailInputController.text = newSubject;
|
||||
}
|
||||
|
||||
void _initAttachments(ComposerArguments arguments) {
|
||||
if (arguments.attachments?.isNotEmpty == true) {
|
||||
initialAttachments = arguments.attachments!;
|
||||
uploadController.initializeUploadAttachments(
|
||||
arguments.attachments!.listAttachmentsDisplayedOutSide);
|
||||
}
|
||||
if (PlatformInfo.isWeb) {
|
||||
expandModeAttachments.value = ExpandMode.EXPAND;
|
||||
void _initAttachments(List<Attachment> attachments) {
|
||||
if (attachments.isNotEmpty) {
|
||||
initialAttachments = attachments;
|
||||
uploadController.initializeUploadAttachments(attachments.listAttachmentsDisplayedOutSide);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -438,7 +512,6 @@ class ComposerController extends BaseController {
|
||||
.toList();
|
||||
|
||||
if (listIdentities.isNotEmpty) {
|
||||
_initTextEditor = null;
|
||||
await selectIdentity(listIdentities.first);
|
||||
}
|
||||
}
|
||||
@@ -446,113 +519,49 @@ class ComposerController extends BaseController {
|
||||
_autoFocusFieldWhenLauncher();
|
||||
}
|
||||
|
||||
String? _getHeaderEmailQuoted(BuildContext context, ComposerArguments arguments) {
|
||||
final presentationEmail = arguments.presentationEmail;
|
||||
if (presentationEmail != null) {
|
||||
final locale = Localizations.localeOf(context).toLanguageTag();
|
||||
log('ComposerController::_getHeaderEmailQuoted(): emailActionType: ${arguments.emailActionType}');
|
||||
switch(arguments.emailActionType) {
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
final receivedAt = presentationEmail.receivedAt;
|
||||
final emailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
|
||||
return AppLocalizations.of(context).header_email_quoted(
|
||||
receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale),
|
||||
emailAddress);
|
||||
case EmailActionType.forward:
|
||||
var headerQuoted = '------- ${AppLocalizations.of(context).forwarded_message} -------'.addNewLineTag();
|
||||
|
||||
final subject = presentationEmail.subject ?? '';
|
||||
final receivedAt = presentationEmail.receivedAt;
|
||||
final fromEmailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final toEmailAddress = presentationEmail.to.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final ccEmailAddress = presentationEmail.cc.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final bccEmailAddress = presentationEmail.bcc.listEmailAddressToString(isFullEmailAddress: true);
|
||||
|
||||
if (subject.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).subject_email}: ')
|
||||
.append(subject)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (receivedAt != null) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).date}: ')
|
||||
.append(receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale))
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (fromEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).from_email_address_prefix}: ')
|
||||
.append(fromEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (toEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).to_email_address_prefix}: ')
|
||||
.append(toEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (ccEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).cc_email_address_prefix}: ')
|
||||
.append(ccEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (bccEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).bcc_email_address_prefix}: ')
|
||||
.append(bccEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
|
||||
return headerQuoted;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void _initEmailAddress(ComposerArguments arguments) {
|
||||
void _initEmailAddress({
|
||||
required PresentationEmail presentationEmail,
|
||||
required EmailActionType actionType,
|
||||
Role? mailboxRole,
|
||||
}) {
|
||||
final recipients = presentationEmail.generateRecipientsEmailAddressForComposer(
|
||||
emailActionType: actionType,
|
||||
mailboxRole: mailboxRole
|
||||
);
|
||||
log('ComposerController::_initEmailAddress:recipients: $recipients');
|
||||
final userProfile = mailboxDashBoardController.userProfile.value;
|
||||
if (arguments.presentationEmail != null && userProfile != null) {
|
||||
final userEmailAddress = EmailAddress(null, userProfile.email);
|
||||
final recipients = arguments.presentationEmail!.generateRecipientsEmailAddressForComposer(
|
||||
arguments.emailActionType,
|
||||
arguments.mailboxRole);
|
||||
|
||||
final isSender = arguments.presentationEmail!.from.asList().every((element) => element.email == userEmailAddress.email);
|
||||
|
||||
if (userProfile != null) {
|
||||
final isSender = presentationEmail.from.asList().every((element) => element.email == userProfile.email);
|
||||
if (isSender) {
|
||||
listToEmailAddress = List.from(recipients.value1);
|
||||
listCcEmailAddress = List.from(recipients.value2);
|
||||
listBccEmailAddress = List.from(recipients.value3);
|
||||
listToEmailAddress = List.from(recipients.value1.toSet());
|
||||
listCcEmailAddress = List.from(recipients.value2.toSet());
|
||||
listBccEmailAddress = List.from(recipients.value3.toSet());
|
||||
} else {
|
||||
listToEmailAddress = List.from(recipients.value1.toSet().filterEmailAddress(userEmailAddress));
|
||||
listCcEmailAddress = List.from(recipients.value2.toSet().filterEmailAddress(userEmailAddress));
|
||||
listBccEmailAddress = List.from(recipients.value3.toSet().filterEmailAddress(userEmailAddress));
|
||||
listToEmailAddress = List.from(recipients.value1.toSet().filterEmailAddress(userProfile.email));
|
||||
listCcEmailAddress = List.from(recipients.value2.toSet().filterEmailAddress(userProfile.email));
|
||||
listBccEmailAddress = List.from(recipients.value3.toSet().filterEmailAddress(userProfile.email));
|
||||
}
|
||||
} else {
|
||||
listToEmailAddress = List.from(recipients.value1.toSet());
|
||||
listCcEmailAddress = List.from(recipients.value2.toSet());
|
||||
listBccEmailAddress = List.from(recipients.value3.toSet());
|
||||
}
|
||||
|
||||
if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) {
|
||||
isInitialRecipient.value = true;
|
||||
toAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
|
||||
if (listCcEmailAddress.isNotEmpty) {
|
||||
listEmailAddressType.add(PrefixEmailAddress.cc);
|
||||
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
|
||||
if (listBccEmailAddress.isNotEmpty) {
|
||||
listEmailAddressType.add(PrefixEmailAddress.bcc);
|
||||
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
} else if (arguments.emailAddress != null) {
|
||||
listToEmailAddress.add(arguments.emailAddress!);
|
||||
if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) {
|
||||
isInitialRecipient.value = true;
|
||||
toAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
|
||||
if (listCcEmailAddress.isNotEmpty) {
|
||||
listEmailAddressType.add(PrefixEmailAddress.cc);
|
||||
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
|
||||
if (listBccEmailAddress.isNotEmpty) {
|
||||
listEmailAddressType.add(PrefixEmailAddress.bcc);
|
||||
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
|
||||
}
|
||||
|
||||
_updateStatusEmailSendButton();
|
||||
}
|
||||
|
||||
@@ -586,13 +595,21 @@ class ComposerController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
String getEmailContentQuotedAsHtml(BuildContext context, ComposerArguments arguments) {
|
||||
final headerEmailQuoted = _getHeaderEmailQuoted(context, arguments);
|
||||
String getEmailContentQuotedAsHtml({
|
||||
required BuildContext context,
|
||||
required String emailContent,
|
||||
required EmailActionType emailActionType,
|
||||
required PresentationEmail presentationEmail,
|
||||
}) {
|
||||
final headerEmailQuoted = emailActionType.getHeaderEmailQuoted(
|
||||
context: context,
|
||||
presentationEmail: presentationEmail
|
||||
);
|
||||
log('ComposerController::getEmailContentQuotedAsHtml(): headerEmailQuoted: $headerEmailQuoted');
|
||||
final headerEmailQuotedAsHtml = headerEmailQuoted != null ? headerEmailQuoted.addBlockTag('cite') : '';
|
||||
|
||||
final trustAsHtml = arguments.emailContents ?? '';
|
||||
final emailQuotedHtml = '${HtmlExtension.editorStartTags}$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}';
|
||||
final headerEmailQuotedAsHtml = headerEmailQuoted != null
|
||||
? headerEmailQuoted.addCiteTag()
|
||||
: '';
|
||||
final emailQuotedHtml = '${HtmlExtension.editorStartTags}$headerEmailQuotedAsHtml${emailContent.addBlockQuoteTag()}';
|
||||
|
||||
return emailQuotedHtml;
|
||||
}
|
||||
@@ -789,13 +806,13 @@ class ComposerController extends BaseController {
|
||||
|
||||
if (arguments != null && accountId != null && userProfile != null && session != null) {
|
||||
final createdEmail = await _generateEmail(context, userProfile, outboxMailboxId: outboxMailboxId);
|
||||
final emailRequest = arguments.sendingEmail != null
|
||||
final emailRequest = arguments.emailActionType == EmailActionType.editSendingEmail
|
||||
? arguments.sendingEmail!.toEmailRequest(newEmail: createdEmail)
|
||||
: EmailRequest(
|
||||
email: createdEmail,
|
||||
sentMailboxId: sentMailboxId,
|
||||
identityId: identitySelected.value?.id,
|
||||
emailIdDestroyed: arguments.emailActionType == EmailActionType.edit
|
||||
emailIdDestroyed: arguments.emailActionType == EmailActionType.editDraft
|
||||
? arguments.presentationEmail?.id
|
||||
: null,
|
||||
emailIdAnsweredOrForwarded: arguments.presentationEmail?.id,
|
||||
@@ -952,36 +969,40 @@ class ComposerController extends BaseController {
|
||||
uploadController.deleteFileUploaded(uploadId);
|
||||
}
|
||||
|
||||
Future<bool> _isEmailChanged(
|
||||
BuildContext context,
|
||||
ComposerArguments arguments,
|
||||
) async {
|
||||
Future<bool> _isEmailChanged({
|
||||
required BuildContext context,
|
||||
required EmailActionType emailActionType,
|
||||
PresentationEmail? presentationEmail,
|
||||
Role? mailboxRole,
|
||||
}) async {
|
||||
final newEmailBody = await _getEmailBodyText(context, changedEmail: true);
|
||||
log('ComposerController::_isEmailChanged(): newEmailBody: $newEmailBody');
|
||||
var oldEmailBody = _initTextEditor ?? '';
|
||||
final oldEmailBody = _initTextEditor ?? '';
|
||||
log('ComposerController::_isEmailChanged(): oldEmailBody: $oldEmailBody');
|
||||
final isEmailBodyChanged = !oldEmailBody.trim().isSame(newEmailBody.trim());
|
||||
log('ComposerController::_isEmailChanged(): isEmailBodyChanged: $isEmailBodyChanged');
|
||||
|
||||
final newEmailSubject = subjectEmail.value ?? '';
|
||||
final titleEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
|
||||
final oldEmailSubject = arguments.emailActionType == EmailActionType.edit ? titleEmail : '';
|
||||
final oldEmailSubject = emailActionType == EmailActionType.editDraft
|
||||
? presentationEmail?.getEmailTitle().trim() ?? ''
|
||||
: '';
|
||||
final isEmailSubjectChanged = !oldEmailSubject.trim().isSame(newEmailSubject.trim());
|
||||
|
||||
final recipients = arguments.presentationEmail
|
||||
?.generateRecipientsEmailAddressForComposer(arguments.emailActionType, arguments.mailboxRole)
|
||||
?? const Tuple3(<EmailAddress>[], <EmailAddress>[], <EmailAddress>[]);
|
||||
final recipients = presentationEmail
|
||||
?.generateRecipientsEmailAddressForComposer(
|
||||
emailActionType: emailActionType,
|
||||
mailboxRole: mailboxRole
|
||||
) ?? const Tuple3(<EmailAddress>[], <EmailAddress>[], <EmailAddress>[]);
|
||||
|
||||
final newToEmailAddress = listToEmailAddress;
|
||||
final oldToEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value1 : [];
|
||||
final oldToEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value1 : [];
|
||||
final isToEmailAddressChanged = !oldToEmailAddress.isSame(newToEmailAddress);
|
||||
|
||||
final newCcEmailAddress = listCcEmailAddress;
|
||||
final oldCcEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value2 : [];
|
||||
final oldCcEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value2 : [];
|
||||
final isCcEmailAddressChanged = !oldCcEmailAddress.isSame(newCcEmailAddress);
|
||||
|
||||
final newBccEmailAddress = listBccEmailAddress;
|
||||
final oldBccEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value3 : [];
|
||||
final oldBccEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value3 : [];
|
||||
final isBccEmailAddressChanged = !oldBccEmailAddress.isSame(newBccEmailAddress);
|
||||
|
||||
final isAttachmentsChanged = !initialAttachments.isSame(uploadController.attachmentsUploaded.toList());
|
||||
@@ -999,36 +1020,60 @@ class ComposerController extends BaseController {
|
||||
clearFocusEditor(context);
|
||||
|
||||
final arguments = composerArguments.value;
|
||||
final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts];
|
||||
final userProfile = mailboxDashBoardController.userProfile.value;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
|
||||
if (arguments != null && userProfile != null && accountId != null && session != null) {
|
||||
final isChanged = await _isEmailChanged(context, arguments);
|
||||
if (isChanged && context.mounted) {
|
||||
final newEmail = await _generateEmail(
|
||||
context,
|
||||
userProfile,
|
||||
asDrafts: true,
|
||||
draftMailboxId: draftMailboxId);
|
||||
final oldEmail = arguments.presentationEmail;
|
||||
|
||||
if (arguments.emailActionType == EmailActionType.edit && oldEmail != null && oldEmail.id != null) {
|
||||
mailboxDashBoardController.consumeState(
|
||||
_updateEmailDraftsInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
newEmail,
|
||||
oldEmail.id!
|
||||
)
|
||||
);
|
||||
} else {
|
||||
mailboxDashBoardController.consumeState(_saveEmailAsDraftsInteractor.execute(session, accountId, newEmail));
|
||||
}
|
||||
|
||||
uploadController.clearInlineFileUploaded();
|
||||
if (arguments == null ||
|
||||
userProfile == null ||
|
||||
session == null ||
|
||||
accountId == null
|
||||
) {
|
||||
if (PlatformInfo.isWeb) {
|
||||
mailboxDashBoardController.closeComposerOverlay();
|
||||
} else {
|
||||
if (canPop) popBack();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final isChanged = await _isEmailChanged(
|
||||
context: context,
|
||||
emailActionType: arguments.emailActionType,
|
||||
presentationEmail: arguments.presentationEmail,
|
||||
mailboxRole: arguments.mailboxRole
|
||||
);
|
||||
|
||||
if (isChanged && context.mounted) {
|
||||
final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts];
|
||||
|
||||
final newEmail = await _generateEmail(
|
||||
context,
|
||||
userProfile,
|
||||
asDrafts: true,
|
||||
draftMailboxId: draftMailboxId
|
||||
);
|
||||
|
||||
if (arguments.emailActionType == EmailActionType.editDraft) {
|
||||
mailboxDashBoardController.consumeState(
|
||||
_updateEmailDraftsInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
newEmail,
|
||||
arguments.presentationEmail!.id!
|
||||
)
|
||||
);
|
||||
} else {
|
||||
mailboxDashBoardController.consumeState(
|
||||
_saveEmailAsDraftsInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
newEmail
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
uploadController.clearInlineFileUploaded();
|
||||
}
|
||||
|
||||
if (PlatformInfo.isWeb) {
|
||||
@@ -1087,90 +1132,109 @@ class ComposerController extends BaseController {
|
||||
return listFileInfo;
|
||||
}
|
||||
|
||||
void _getEmailContentAction(ComposerArguments arguments) async {
|
||||
|
||||
final listSharedMediaFile = arguments.listSharedMediaFile;
|
||||
if (listSharedMediaFile != null && listSharedMediaFile.isNotEmpty) {
|
||||
final listImageSharedMediaFile = listSharedMediaFile.where((element) => element.type == SharedMediaType.IMAGE);
|
||||
final listFileAttachmentSharedMediaFile = listSharedMediaFile.where((element) => element.type != SharedMediaType.IMAGE);
|
||||
if (listImageSharedMediaFile.isNotEmpty) {
|
||||
final listInlineImage = covertListSharedMediaFileToInlineImage(arguments.listSharedMediaFile!);
|
||||
for (var e in listInlineImage) {
|
||||
_uploadInlineAttachmentsAction(e.fileInfo!);
|
||||
}
|
||||
void _addAttachmentFromFileShare(List<SharedMediaFile> listSharedMediaFile) {
|
||||
final listImageSharedMediaFile = listSharedMediaFile.where((element) => element.type == SharedMediaType.IMAGE);
|
||||
final listFileAttachmentSharedMediaFile = listSharedMediaFile.where((element) => element.type != SharedMediaType.IMAGE);
|
||||
if (listImageSharedMediaFile.isNotEmpty) {
|
||||
final listInlineImage = covertListSharedMediaFileToInlineImage(listSharedMediaFile);
|
||||
for (var e in listInlineImage) {
|
||||
_uploadInlineAttachmentsAction(e.fileInfo!);
|
||||
}
|
||||
if (listFileAttachmentSharedMediaFile.isNotEmpty) {
|
||||
final listFile = covertListSharedMediaFileToFileInfo(arguments.listSharedMediaFile!);
|
||||
if (uploadController.hasEnoughMaxAttachmentSize(listFiles: listFile)) {
|
||||
_uploadAttachmentsAction(listFile);
|
||||
} else {
|
||||
if (currentContext != null) {
|
||||
showConfirmDialogAction(
|
||||
currentContext!,
|
||||
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(
|
||||
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)),
|
||||
AppLocalizations.of(currentContext!).got_it,
|
||||
onConfirmAction: () => {},
|
||||
title: AppLocalizations.of(currentContext!).maximum_files_size,
|
||||
hasCancelButton: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (listFileAttachmentSharedMediaFile.isNotEmpty) {
|
||||
final listFile = covertListSharedMediaFileToFileInfo(listSharedMediaFile);
|
||||
if (uploadController.hasEnoughMaxAttachmentSize(listFiles: listFile)) {
|
||||
_uploadAttachmentsAction(listFile);
|
||||
} else {
|
||||
if (currentContext != null) {
|
||||
showConfirmDialogAction(
|
||||
currentContext!,
|
||||
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(
|
||||
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)),
|
||||
AppLocalizations.of(currentContext!).got_it,
|
||||
title: AppLocalizations.of(currentContext!).maximum_files_size,
|
||||
hasCancelButton: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (arguments.emailContents != null && arguments.emailContents!.isNotEmpty && arguments.sendingEmail != null) {
|
||||
_emailContents = arguments.emailContents;
|
||||
emailContentsViewState.value = Right(
|
||||
GetEmailContentSuccess(
|
||||
emailContent: _emailContents!,
|
||||
emailContentDisplayed: '',
|
||||
attachments: [],
|
||||
emailCurrent: arguments.presentationEmail?.toEmail()
|
||||
)
|
||||
);
|
||||
} else {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
final accountId = mailboxDashBoardController.sessionCurrent?.accounts.keys.first;
|
||||
final emailId = arguments.presentationEmail?.id;
|
||||
if (session != null && emailId != null && baseDownloadUrl != null && accountId != null) {
|
||||
consumeState(_getEmailContentInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
emailId,
|
||||
baseDownloadUrl,
|
||||
composeEmail: true,
|
||||
draftsEmail: arguments.presentationEmail?.isDraft ?? false
|
||||
));
|
||||
void _getEmailContentFromSendingEmail(SendingEmail sendingEmail) {
|
||||
consumeState(Stream.value(
|
||||
Right(GetEmailContentSuccess(
|
||||
htmlEmailContent: sendingEmail.presentationEmail.emailContentList.asHtmlString,
|
||||
attachments: sendingEmail.email.allAttachments,
|
||||
emailCurrent: sendingEmail.email
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
void _getEmailContentFromSessionStorageBrowser(String content) {
|
||||
consumeState(Stream.value(
|
||||
Right(GetEmailContentSuccess(
|
||||
htmlEmailContent: content,
|
||||
attachments: [],
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
void _getEmailContentFromContentShared(String content) {
|
||||
consumeState(Stream.value(
|
||||
Right(GetEmailContentSuccess(
|
||||
htmlEmailContent: content,
|
||||
attachments: [],
|
||||
))
|
||||
));
|
||||
}
|
||||
|
||||
void _getEmailContentFromEmailId({required EmailId emailId, bool isDraftEmail = false}) {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
if (session != null && accountId != null) {
|
||||
TransformConfiguration transformConfiguration = TransformConfiguration.standardConfiguration;
|
||||
if (isDraftEmail) {
|
||||
transformConfiguration = TransformConfiguration.forDraftsEmail();
|
||||
} else if (PlatformInfo.isWeb) {
|
||||
transformConfiguration = TransformConfiguration.forComposeEmailPlatformWeb();
|
||||
}
|
||||
|
||||
consumeState(_getEmailContentInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
emailId,
|
||||
mailboxDashBoardController.baseDownloadUrl,
|
||||
transformConfiguration
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
void _getEmailContentOffLineSuccess(GetEmailContentFromCacheSuccess success) {
|
||||
if (success.attachments.isNotEmpty) {
|
||||
initialAttachments = success.attachments;
|
||||
uploadController.initializeUploadAttachments(
|
||||
success.attachments.listAttachmentsDisplayedOutSide);
|
||||
}
|
||||
_initAttachments(success.attachments);
|
||||
emailContentsViewState.value = Right(success);
|
||||
_emailContents = success.emailContent;
|
||||
}
|
||||
|
||||
void _getEmailContentSuccess(GetEmailContentSuccess success) {
|
||||
if (success.attachments.isNotEmpty) {
|
||||
initialAttachments = success.attachments;
|
||||
uploadController.initializeUploadAttachments(
|
||||
success.attachments.listAttachmentsDisplayedOutSide);
|
||||
}
|
||||
_initAttachments(success.attachments);
|
||||
emailContentsViewState.value = Right(success);
|
||||
_emailContents = success.emailContent;
|
||||
}
|
||||
|
||||
void _transformHtmlEmailContent(String? emailContent) {
|
||||
emailContentsViewState(Right(TransformHtmlEmailContentLoading()));
|
||||
if (emailContent?.isEmpty == true) {
|
||||
consumeState(Stream.value(Left(TransformHtmlEmailContentFailure(EmptyEmailContentException()))));
|
||||
} else {
|
||||
consumeState(_transformHtmlEmailContentInteractor.execute(
|
||||
emailContent!,
|
||||
TransformConfiguration.forReplyForwardEmail()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
String getEmailAddressSender() {
|
||||
final arguments = composerArguments.value;
|
||||
if (arguments != null) {
|
||||
if (arguments.emailActionType == EmailActionType.edit) {
|
||||
if (arguments.emailActionType == EmailActionType.editDraft) {
|
||||
return arguments.presentationEmail?.from?.first.emailAddress ?? '';
|
||||
} else {
|
||||
return mailboxDashBoardController.userProfile.value?.email ?? '';
|
||||
|
||||
@@ -15,9 +15,11 @@ import 'package:get/get.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:rich_text_composer/rich_text_composer.dart';
|
||||
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/cupertino_loading_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/base_composer_view.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.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/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
@@ -276,38 +278,96 @@ class ComposerView extends BaseComposerView {
|
||||
switch(argsComposer.emailActionType) {
|
||||
case EmailActionType.compose:
|
||||
case EmailActionType.composeFromEmailAddress:
|
||||
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
|
||||
case EmailActionType.edit:
|
||||
return controller.emailContentsViewState.value.fold(
|
||||
(failure) => _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags),
|
||||
case EmailActionType.composeFromFileShared:
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: HtmlExtension.editorStartTags
|
||||
);
|
||||
case EmailActionType.editDraft:
|
||||
case EmailActionType.editSendingEmail:
|
||||
case EmailActionType.composeFromContentShared:
|
||||
case EmailActionType.reopenComposerBrowser:
|
||||
final emailContentsViewState = controller.emailContentsViewState.value;
|
||||
if (emailContentsViewState == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return emailContentsViewState.fold(
|
||||
(failure) => _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: HtmlExtension.editorStartTags
|
||||
),
|
||||
(success) {
|
||||
if (success is GetEmailContentLoading) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: loadingWidget,
|
||||
);
|
||||
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
|
||||
} else if (success is GetEmailContentSuccess) {
|
||||
var contentHtml = success.emailContent;
|
||||
var contentHtml = success.htmlEmailContent;
|
||||
if (contentHtml.isEmpty == true) {
|
||||
contentHtml = HtmlExtension.editorStartTags;
|
||||
}
|
||||
return _buildHtmlEditor(context, initialContent: contentHtml);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: contentHtml
|
||||
);
|
||||
} else {
|
||||
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: HtmlExtension.editorStartTags
|
||||
);
|
||||
}
|
||||
});
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
case EmailActionType.forward:
|
||||
var contentHtml = controller.getEmailContentQuotedAsHtml(
|
||||
context,
|
||||
argsComposer);
|
||||
if (contentHtml.isEmpty == true) {
|
||||
contentHtml = HtmlExtension.editorStartTags;
|
||||
final emailContentsViewState = controller.emailContentsViewState.value;
|
||||
if (emailContentsViewState == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _buildHtmlEditor(context, initialContent: contentHtml);
|
||||
return emailContentsViewState.fold(
|
||||
(failure) {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: '',
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: emailContentQuoted
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
if (success is TransformHtmlEmailContentLoading) {
|
||||
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
|
||||
} else if (success is TransformHtmlEmailContentSuccess) {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: success.htmlContent,
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: emailContentQuoted
|
||||
);
|
||||
} else {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: '',
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: emailContentQuoted
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
default:
|
||||
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
initialContent: HtmlExtension.editorStartTags
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,12 +10,14 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:pointer_interceptor/pointer_interceptor.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/cupertino_loading_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/base_composer_view.dart';
|
||||
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/email_editor_widget.dart';
|
||||
import 'package:tmail_ui_user/features/composer/presentation/widgets/toolbar_rich_text_builder.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/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/utils/app_utils.dart';
|
||||
|
||||
@@ -385,46 +387,96 @@ class ComposerView extends BaseComposerView {
|
||||
switch(argsComposer.emailActionType) {
|
||||
case EmailActionType.compose:
|
||||
case EmailActionType.composeFromEmailAddress:
|
||||
case EmailActionType.composeFromFileShared:
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags);
|
||||
case EmailActionType.edit:
|
||||
return controller.emailContentsViewState.value.fold(
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags
|
||||
);
|
||||
case EmailActionType.editDraft:
|
||||
case EmailActionType.editSendingEmail:
|
||||
case EmailActionType.composeFromContentShared:
|
||||
case EmailActionType.reopenComposerBrowser:
|
||||
final emailContentsViewState = controller.emailContentsViewState.value;
|
||||
if (emailContentsViewState == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return emailContentsViewState.fold(
|
||||
(failure) => _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags),
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags
|
||||
),
|
||||
(success) {
|
||||
if (success is GetEmailContentLoading) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: loadingWidget,
|
||||
);
|
||||
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
|
||||
} else if (success is GetEmailContentSuccess) {
|
||||
var contentHtml = success.emailContent;
|
||||
var contentHtml = success.htmlEmailContent;
|
||||
if (contentHtml.isEmpty == true) {
|
||||
contentHtml = HtmlExtension.editorStartTags;
|
||||
}
|
||||
return _buildHtmlEditor(context, currentTextEditor ?? contentHtml);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? contentHtml
|
||||
);
|
||||
} else {
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags);
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags
|
||||
);
|
||||
}
|
||||
});
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
case EmailActionType.forward:
|
||||
var contentHtml = controller.getEmailContentQuotedAsHtml(
|
||||
context,
|
||||
argsComposer);
|
||||
if (contentHtml.isEmpty == true) {
|
||||
contentHtml = HtmlExtension.editorStartTags;
|
||||
final emailContentsViewState = controller.emailContentsViewState.value;
|
||||
if (emailContentsViewState == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return _buildHtmlEditor(context, currentTextEditor ?? contentHtml);
|
||||
return emailContentsViewState.fold(
|
||||
(failure) {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: '',
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? emailContentQuoted
|
||||
);
|
||||
},
|
||||
(success) {
|
||||
if (success is TransformHtmlEmailContentLoading) {
|
||||
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
|
||||
} else if (success is TransformHtmlEmailContentSuccess) {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: success.htmlContent,
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? emailContentQuoted
|
||||
);
|
||||
} else {
|
||||
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
|
||||
context: context,
|
||||
emailContent: '',
|
||||
emailActionType: argsComposer.emailActionType,
|
||||
presentationEmail: argsComposer.presentationEmail!
|
||||
);
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? emailContentQuoted
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
default:
|
||||
return _buildHtmlEditor(
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags);
|
||||
context,
|
||||
currentTextEditor ?? HtmlExtension.editorStartTags
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,25 +1,35 @@
|
||||
|
||||
import 'package:core/presentation/extensions/html_extension.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/list_email_address_extension.dart';
|
||||
import 'package:model/extensions/utc_date_extension.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
extension EmailActionTypeExtension on EmailActionType {
|
||||
String getSubjectComposer(BuildContext context, String subject) {
|
||||
String getSubjectComposer(BuildContext? context, String subject) {
|
||||
switch(this) {
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
if (subject.toLowerCase().startsWith('re:')) {
|
||||
return subject;
|
||||
} else {
|
||||
return '${AppLocalizations.of(context).prefix_reply_email} $subject';
|
||||
return context != null
|
||||
? '${AppLocalizations.of(context).prefix_reply_email} $subject'
|
||||
: subject;
|
||||
}
|
||||
case EmailActionType.forward:
|
||||
if (subject.toLowerCase().startsWith('fwd:')) {
|
||||
return subject;
|
||||
} else {
|
||||
return '${AppLocalizations.of(context).prefix_forward_email} $subject';
|
||||
return context != null
|
||||
? '${AppLocalizations.of(context).prefix_forward_email} $subject'
|
||||
: subject;
|
||||
}
|
||||
case EmailActionType.edit:
|
||||
case EmailActionType.editDraft:
|
||||
case EmailActionType.editSendingEmail:
|
||||
case EmailActionType.reopenComposerBrowser:
|
||||
return subject;
|
||||
default:
|
||||
return '';
|
||||
@@ -40,4 +50,71 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
String? getHeaderEmailQuoted({
|
||||
required BuildContext context,
|
||||
required PresentationEmail presentationEmail
|
||||
}) {
|
||||
final locale = Localizations.localeOf(context).toLanguageTag();
|
||||
switch(this) {
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyAll:
|
||||
final receivedAt = presentationEmail.receivedAt;
|
||||
final emailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
|
||||
return AppLocalizations.of(context).header_email_quoted(
|
||||
receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale),
|
||||
emailAddress
|
||||
);
|
||||
case EmailActionType.forward:
|
||||
var headerQuoted = '------- ${AppLocalizations.of(context).forwarded_message} -------'.addNewLineTag();
|
||||
|
||||
final subject = presentationEmail.subject ?? '';
|
||||
final receivedAt = presentationEmail.receivedAt;
|
||||
final fromEmailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final toEmailAddress = presentationEmail.to.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final ccEmailAddress = presentationEmail.cc.listEmailAddressToString(isFullEmailAddress: true);
|
||||
final bccEmailAddress = presentationEmail.bcc.listEmailAddressToString(isFullEmailAddress: true);
|
||||
|
||||
if (subject.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).subject_email}: ')
|
||||
.append(subject)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (receivedAt != null) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).date}: ')
|
||||
.append(receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale))
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (fromEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).from_email_address_prefix}: ')
|
||||
.append(fromEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (toEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).to_email_address_prefix}: ')
|
||||
.append(toEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (ccEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).cc_email_address_prefix}: ')
|
||||
.append(ccEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
if (bccEmailAddress.isNotEmpty) {
|
||||
headerQuoted = headerQuoted
|
||||
.append('${AppLocalizations.of(context).bcc_email_address_prefix}: ')
|
||||
.append(bccEmailAddress)
|
||||
.addNewLineTag();
|
||||
}
|
||||
|
||||
return headerQuoted;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class ComposerStyle {
|
||||
if (responsiveUtils.isPortraitMobile(context) || responsiveUtils.isLandscapeMobile(context)) {
|
||||
return const EdgeInsetsDirectional.symmetric(horizontal: 16);
|
||||
} else {
|
||||
return const EdgeInsetsDirectional.only(start: 88, end: 48);
|
||||
return const EdgeInsetsDirectional.only(start: 88, end: 48, top: 8);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:model/model.dart';
|
||||
|
||||
abstract class HtmlDataSource {
|
||||
Future<EmailContent> transformEmailContent(
|
||||
EmailContent emailContent,
|
||||
Map<String, String> mapUrlDownloadCID,
|
||||
{bool draftsEmail = false}
|
||||
Map<String, String> mapCidImageDownloadUrl,
|
||||
TransformConfiguration transformConfiguration
|
||||
);
|
||||
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent);
|
||||
Future<String> transformHtmlEmailContent(
|
||||
String htmlContent,
|
||||
TransformConfiguration configuration
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.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';
|
||||
@@ -13,22 +14,25 @@ class HtmlDataSourceImpl extends HtmlDataSource {
|
||||
@override
|
||||
Future<EmailContent> transformEmailContent(
|
||||
EmailContent emailContent,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
{bool draftsEmail = false}
|
||||
Map<String, String> mapCidImageDownloadUrl,
|
||||
TransformConfiguration transformConfiguration
|
||||
) {
|
||||
return Future.sync(() async {
|
||||
return await _htmlAnalyzer.transformEmailContent(
|
||||
emailContent,
|
||||
mapUrlDownloadCID,
|
||||
draftsEmail: draftsEmail
|
||||
mapCidImageDownloadUrl,
|
||||
transformConfiguration
|
||||
);
|
||||
}).catchError(_exceptionThrower.throwException);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent) {
|
||||
Future<String> transformHtmlEmailContent(String htmlContent, TransformConfiguration configuration) {
|
||||
return Future.sync(() async {
|
||||
return await _htmlAnalyzer.addTooltipWhenHoverOnLink(emailContent);
|
||||
return await _htmlAnalyzer.transformHtmlEmailContent(
|
||||
htmlContent,
|
||||
configuration
|
||||
);
|
||||
}).catchError(_exceptionThrower.throwException);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,5 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:core/data/network/dio_client.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/dom/add_tooltip_link_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/html_transform.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/text/sanitize_autolink_html_transformers.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:model/email/email_content.dart';
|
||||
@@ -14,32 +8,27 @@ import 'package:model/email/email_content_type.dart';
|
||||
class HtmlAnalyzer {
|
||||
|
||||
final HtmlTransform _htmlTransform;
|
||||
final HtmlEscape _htmlEscape;
|
||||
|
||||
HtmlAnalyzer(this._htmlTransform, this._htmlEscape);
|
||||
HtmlAnalyzer(this._htmlTransform);
|
||||
|
||||
Future<EmailContent> transformEmailContent(
|
||||
EmailContent emailContent,
|
||||
Map<String, String>? mapUrlDownloadCID,
|
||||
{bool draftsEmail = false}
|
||||
Map<String, String> mapCidImageDownloadUrl,
|
||||
TransformConfiguration transformConfiguration
|
||||
) async {
|
||||
switch(emailContent.type) {
|
||||
case EmailContentType.textHtml:
|
||||
final htmlContent = await _htmlTransform.transformToHtml(
|
||||
contentHtml: emailContent.content,
|
||||
mapUrlDownloadCID: mapUrlDownloadCID,
|
||||
transformConfiguration: draftsEmail
|
||||
? TransformConfiguration.create(customDomTransformers: TransformConfiguration.domTransformersForDraftEmail)
|
||||
: null
|
||||
htmlContent: emailContent.content,
|
||||
mapCidImageDownloadUrl: mapCidImageDownloadUrl,
|
||||
transformConfiguration: transformConfiguration
|
||||
);
|
||||
|
||||
return EmailContent(emailContent.type, htmlContent);
|
||||
case EmailContentType.textPlain:
|
||||
final message = _htmlTransform.transformToTextPlain(
|
||||
content: emailContent.content,
|
||||
transformConfiguration: TransformConfiguration.create(
|
||||
customTextTransformers: [SanitizeAutolinkHtmlTransformers(_htmlEscape)]
|
||||
)
|
||||
transformConfiguration: transformConfiguration
|
||||
);
|
||||
return EmailContent(emailContent.type, message);
|
||||
default:
|
||||
@@ -47,21 +36,6 @@ class HtmlAnalyzer {
|
||||
}
|
||||
}
|
||||
|
||||
Future<EmailContent> addTooltipWhenHoverOnLink(EmailContent emailContent) async {
|
||||
switch(emailContent.type) {
|
||||
case EmailContentType.textHtml:
|
||||
final htmlContent = await _htmlTransform.transformToHtml(
|
||||
contentHtml: emailContent.content,
|
||||
transformConfiguration: TransformConfiguration.create(
|
||||
customDomTransformers: [const AddTooltipLinkTransformer()]
|
||||
)
|
||||
);
|
||||
return EmailContent(emailContent.type, htmlContent);
|
||||
default:
|
||||
return emailContent;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<String>> getListLinkCalendarEvent(String emailContents) async {
|
||||
final document = parse(emailContents);
|
||||
final linkElements = document.querySelectorAll('a.part-button');
|
||||
@@ -71,4 +45,15 @@ class HtmlAnalyzer {
|
||||
.toList();
|
||||
return listLink;
|
||||
}
|
||||
|
||||
Future<String> transformHtmlEmailContent(
|
||||
String htmlContent,
|
||||
TransformConfiguration configuration
|
||||
) async {
|
||||
final htmlContentTransformed = await _htmlTransform.transformToHtml(
|
||||
htmlContent: htmlContent,
|
||||
transformConfiguration: configuration
|
||||
);
|
||||
return htmlContentTransformed;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import 'package:core/data/model/source_type/data_source_type.dart';
|
||||
import 'package:core/data/network/download/downloaded_response.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
@@ -109,22 +110,16 @@ class EmailRepositoryImpl extends EmailRepository {
|
||||
|
||||
@override
|
||||
Future<List<EmailContent>> transformEmailContent(
|
||||
List<EmailContent> emailContents,
|
||||
List<Attachment> attachmentInlines,
|
||||
String? baseUrlDownload,
|
||||
AccountId accountId,
|
||||
{bool draftsEmail = false}
|
||||
) async {
|
||||
final mapUrlDownloadCID = {
|
||||
for (var attachment in attachmentInlines)
|
||||
attachment.cid! : attachment.getDownloadUrl(baseUrlDownload!, accountId)
|
||||
};
|
||||
List<EmailContent> emailContents,
|
||||
Map<String, String> mapCidImageDownloadUrl,
|
||||
TransformConfiguration transformConfiguration
|
||||
) async {
|
||||
return await Future.wait(emailContents
|
||||
.map((emailContent) async {
|
||||
return await _htmlDataSource.transformEmailContent(
|
||||
emailContent,
|
||||
mapUrlDownloadCID,
|
||||
draftsEmail: draftsEmail
|
||||
mapCidImageDownloadUrl,
|
||||
transformConfiguration,
|
||||
);
|
||||
})
|
||||
.toList());
|
||||
@@ -173,13 +168,6 @@ class EmailRepositoryImpl extends EmailRepository {
|
||||
return emailDataSource[DataSourceType.network]!.deleteEmailPermanently(session, accountId, emailId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<EmailContent>> addTooltipWhenHoverOnLink(List<EmailContent> emailContents) {
|
||||
return Future.wait(emailContents
|
||||
.map((emailContent) => _htmlDataSource.addTooltipWhenHoverOnLink(emailContent))
|
||||
.toList());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<jmap.State?> getEmailState(Session session, AccountId accountId) {
|
||||
return _stateDataSource.getState(accountId, session.username, StateType.email);
|
||||
@@ -219,4 +207,9 @@ class EmailRepositoryImpl extends EmailRepository {
|
||||
Future<DetailedEmail> getStoredNewEmail(Session session, AccountId accountId, EmailId emailId) {
|
||||
return emailDataSource[DataSourceType.hiveCache]!.getStoredNewEmail(session, accountId, emailId);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> transformHtmlEmailContent(String htmlContent, TransformConfiguration configuration) {
|
||||
return _htmlDataSource.transformHtmlEmailContent(htmlContent, configuration);
|
||||
}
|
||||
}
|
||||
@@ -3,4 +3,6 @@ class CannotDeleteOldEmailException implements Exception {}
|
||||
|
||||
class NotFoundEmailException implements Exception {}
|
||||
|
||||
class NotFoundEmailContentException implements Exception {}
|
||||
class NotFoundEmailContentException implements Exception {}
|
||||
|
||||
class EmptyEmailContentException implements Exception {}
|
||||
@@ -4,6 +4,7 @@ import 'dart:typed_data';
|
||||
import 'package:core/data/network/download/downloaded_response.dart';
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
@@ -69,14 +70,10 @@ abstract class EmailRepository {
|
||||
|
||||
Future<List<EmailContent>> transformEmailContent(
|
||||
List<EmailContent> emailContents,
|
||||
List<Attachment> attachmentInlines,
|
||||
String? baseUrlDownload,
|
||||
AccountId accountId,
|
||||
{bool draftsEmail = false}
|
||||
Map<String, String> mapCidImageDownloadUrl,
|
||||
TransformConfiguration transformConfiguration
|
||||
);
|
||||
|
||||
Future<List<EmailContent>> addTooltipWhenHoverOnLink(List<EmailContent> emailContents);
|
||||
|
||||
Future<Email> saveEmailAsDrafts(Session session, AccountId accountId, Email email);
|
||||
|
||||
Future<bool> removeEmailDrafts(Session session, AccountId accountId, EmailId emailId);
|
||||
@@ -102,4 +99,9 @@ abstract class EmailRepository {
|
||||
Future<DetailedEmail> getStoredOpenedEmail(Session session, AccountId accountId, EmailId emailId);
|
||||
|
||||
Future<DetailedEmail> getStoredNewEmail(Session session, AccountId accountId, EmailId emailId);
|
||||
|
||||
Future<String> transformHtmlEmailContent(
|
||||
String htmlContent,
|
||||
TransformConfiguration configuration
|
||||
);
|
||||
}
|
||||
@@ -6,41 +6,38 @@ import 'package:model/email/attachment.dart';
|
||||
class GetEmailContentLoading extends LoadingState {}
|
||||
|
||||
class GetEmailContentSuccess extends UIState {
|
||||
final String emailContent;
|
||||
final String emailContentDisplayed;
|
||||
final String htmlEmailContent;
|
||||
final List<Attachment> attachments;
|
||||
final Email? emailCurrent;
|
||||
|
||||
GetEmailContentSuccess({
|
||||
required this.emailContent,
|
||||
required this.emailContentDisplayed,
|
||||
required this.attachments,
|
||||
required this.emailCurrent
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
emailContent,
|
||||
emailContentDisplayed,
|
||||
attachments,
|
||||
emailCurrent
|
||||
];
|
||||
}
|
||||
|
||||
class GetEmailContentFromCacheSuccess extends UIState {
|
||||
final String emailContent;
|
||||
final List<Attachment> attachments;
|
||||
final Email? emailCurrent;
|
||||
|
||||
GetEmailContentFromCacheSuccess({
|
||||
required this.emailContent,
|
||||
required this.htmlEmailContent,
|
||||
required this.attachments,
|
||||
this.emailCurrent
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
emailContent,
|
||||
htmlEmailContent,
|
||||
attachments,
|
||||
emailCurrent
|
||||
];
|
||||
}
|
||||
|
||||
class GetEmailContentFromCacheSuccess extends UIState {
|
||||
final String htmlEmailContent;
|
||||
final List<Attachment> attachments;
|
||||
final Email? emailCurrent;
|
||||
|
||||
GetEmailContentFromCacheSuccess({
|
||||
required this.htmlEmailContent,
|
||||
required this.attachments,
|
||||
this.emailCurrent
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
htmlEmailContent,
|
||||
attachments,
|
||||
emailCurrent,
|
||||
];
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
|
||||
class TransformHtmlEmailContentLoading extends LoadingState {}
|
||||
|
||||
class TransformHtmlEmailContentSuccess extends UIState {
|
||||
final String htmlContent;
|
||||
|
||||
TransformHtmlEmailContentSuccess(this.htmlContent);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [htmlContent];
|
||||
}
|
||||
|
||||
class TransformHtmlEmailContentFailure extends FeatureFailure {
|
||||
|
||||
TransformHtmlEmailContentFailure(dynamic exception) : super(exception: exception);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@@ -20,19 +21,16 @@ class GetEmailContentInteractor {
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
EmailId emailId,
|
||||
String? baseDownloadUrl,
|
||||
{
|
||||
bool composeEmail = false,
|
||||
bool draftsEmail = false
|
||||
}
|
||||
String baseDownloadUrl,
|
||||
TransformConfiguration transformConfiguration,
|
||||
) async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(GetEmailContentLoading());
|
||||
|
||||
if (PlatformInfo.isMobile) {
|
||||
yield* _getStoredOpenedEmail(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
|
||||
yield* _getStoredOpenedEmail(session, accountId, emailId, baseDownloadUrl, transformConfiguration);
|
||||
} else {
|
||||
yield* _getContentEmailFromServer(session, accountId, emailId, baseDownloadUrl, composeEmail: composeEmail, draftsEmail: draftsEmail);
|
||||
yield* _getContentEmailFromServer(session, accountId, emailId, baseDownloadUrl, transformConfiguration);
|
||||
}
|
||||
} catch (e) {
|
||||
log('GetEmailContentInteractor::execute(): exception = $e');
|
||||
@@ -44,38 +42,31 @@ class GetEmailContentInteractor {
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
EmailId emailId,
|
||||
String? baseDownloadUrl,
|
||||
{
|
||||
bool composeEmail = false,
|
||||
bool draftsEmail = false
|
||||
}
|
||||
String baseDownloadUrl,
|
||||
TransformConfiguration transformConfiguration,
|
||||
) async* {
|
||||
try {
|
||||
final email = await emailRepository.getEmailContent(session, accountId, emailId);
|
||||
|
||||
if (email.emailContentList.isNotEmpty) {
|
||||
final mapCidImageDownloadUrl = email.attachmentsWithCid.toMapCidImageDownloadUrl(
|
||||
accountId: accountId,
|
||||
downloadUrl: baseDownloadUrl
|
||||
);
|
||||
final newEmailContents = await emailRepository.transformEmailContent(
|
||||
email.emailContentList,
|
||||
email.allAttachments.listAttachmentsDisplayedInContent,
|
||||
baseDownloadUrl,
|
||||
accountId,
|
||||
draftsEmail: draftsEmail
|
||||
mapCidImageDownloadUrl,
|
||||
transformConfiguration
|
||||
);
|
||||
|
||||
final newEmailContentsDisplayed = PlatformInfo.isWeb && !composeEmail
|
||||
? await emailRepository.addTooltipWhenHoverOnLink(newEmailContents)
|
||||
: newEmailContents;
|
||||
|
||||
yield Right<Failure, Success>(GetEmailContentSuccess(
|
||||
emailContent: newEmailContents.asHtmlString,
|
||||
emailContentDisplayed: newEmailContentsDisplayed.asHtmlString,
|
||||
htmlEmailContent: newEmailContents.asHtmlString,
|
||||
attachments: email.allAttachments,
|
||||
emailCurrent: email
|
||||
));
|
||||
} else {
|
||||
yield Right<Failure, Success>(GetEmailContentSuccess(
|
||||
emailContent: '',
|
||||
emailContentDisplayed: '',
|
||||
htmlEmailContent: '',
|
||||
attachments: email.allAttachments,
|
||||
emailCurrent: email
|
||||
));
|
||||
@@ -90,17 +81,14 @@ class GetEmailContentInteractor {
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
EmailId emailId,
|
||||
String? baseDownloadUrl,
|
||||
{
|
||||
bool composeEmail = false,
|
||||
bool draftsEmail = false
|
||||
}
|
||||
String baseDownloadUrl,
|
||||
TransformConfiguration transformConfiguration,
|
||||
) async* {
|
||||
try {
|
||||
log('GetEmailContentInteractor::_getStoredOpenedEmail(): CALLED');
|
||||
final detailedEmail = await emailRepository.getStoredOpenedEmail(session, accountId, emailId);
|
||||
yield Right<Failure, Success>(GetEmailContentFromCacheSuccess(
|
||||
emailContent: detailedEmail.htmlEmailContent ?? "",
|
||||
htmlEmailContent: detailedEmail.htmlEmailContent ?? '',
|
||||
attachments: detailedEmail.attachments ?? [],
|
||||
emailCurrent: Email(
|
||||
id: emailId,
|
||||
@@ -115,8 +103,8 @@ class GetEmailContentInteractor {
|
||||
accountId,
|
||||
emailId,
|
||||
baseDownloadUrl,
|
||||
composeEmail: composeEmail,
|
||||
draftsEmail: draftsEmail);
|
||||
transformConfiguration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,17 +112,14 @@ class GetEmailContentInteractor {
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
EmailId emailId,
|
||||
String? baseDownloadUrl,
|
||||
{
|
||||
bool composeEmail = false,
|
||||
bool draftsEmail = false
|
||||
}
|
||||
String baseDownloadUrl,
|
||||
TransformConfiguration transformConfiguration,
|
||||
) async* {
|
||||
try {
|
||||
log('GetEmailContentInteractor::_getStoredNewEmail():CALLED');
|
||||
final detailedEmail = await emailRepository.getStoredNewEmail(session, accountId, emailId);
|
||||
yield Right<Failure, Success>(GetEmailContentFromCacheSuccess(
|
||||
emailContent: detailedEmail.htmlEmailContent ?? "",
|
||||
htmlEmailContent: detailedEmail.htmlEmailContent ?? '',
|
||||
attachments: detailedEmail.attachments ?? [],
|
||||
emailCurrent: Email(
|
||||
id: emailId,
|
||||
@@ -149,8 +134,8 @@ class GetEmailContentInteractor {
|
||||
accountId,
|
||||
emailId,
|
||||
baseDownloadUrl,
|
||||
composeEmail: composeEmail,
|
||||
draftsEmail: draftsEmail);
|
||||
transformConfiguration
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/extensions/utc_date_extension.dart';
|
||||
@@ -23,7 +25,7 @@ class GetListDetailedEmailByIdInteractor {
|
||||
Session session,
|
||||
AccountId accountId,
|
||||
Set<EmailId> emailIds,
|
||||
String? baseDownloadUrl,
|
||||
String baseDownloadUrl,
|
||||
{Set<Comparator>? sort}
|
||||
) async* {
|
||||
try {
|
||||
@@ -57,17 +59,27 @@ class GetListDetailedEmailByIdInteractor {
|
||||
Future<Tuple2<Email, DetailedEmail>> _parsingEmailToDetailedEmail(
|
||||
AccountId accountId,
|
||||
Email email,
|
||||
String? baseDownloadUrl
|
||||
String baseDownloadUrl
|
||||
) async {
|
||||
String? htmlEmailContent;
|
||||
|
||||
final listEmailContent = email.emailContentList;
|
||||
if (listEmailContent.isNotEmpty) {
|
||||
final mapCidImageDownloadUrl = email.attachmentsWithCid.toMapCidImageDownloadUrl(
|
||||
accountId: accountId,
|
||||
downloadUrl: baseDownloadUrl
|
||||
);
|
||||
TransformConfiguration transformConfiguration = TransformConfiguration.standardConfiguration;
|
||||
if (email.isDraft) {
|
||||
transformConfiguration = TransformConfiguration.forDraftsEmail();
|
||||
} else if (PlatformInfo.isWeb) {
|
||||
transformConfiguration = TransformConfiguration.forPreviewEmailPlatformWeb();
|
||||
}
|
||||
final newEmailContents = await _emailRepository.transformEmailContent(
|
||||
listEmailContent,
|
||||
email.allAttachments.listAttachmentsDisplayedInContent,
|
||||
baseDownloadUrl,
|
||||
accountId);
|
||||
email.emailContentList,
|
||||
mapCidImageDownloadUrl,
|
||||
transformConfiguration
|
||||
);
|
||||
|
||||
htmlEmailContent = newEmailContents.asHtmlString;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
|
||||
import 'package:core/presentation/state/failure.dart';
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart';
|
||||
|
||||
class TransformHtmlEmailContentInteractor {
|
||||
final EmailRepository emailRepository;
|
||||
|
||||
TransformHtmlEmailContentInteractor(this.emailRepository);
|
||||
|
||||
Stream<Either<Failure, Success>> execute(
|
||||
String htmlContent,
|
||||
TransformConfiguration configuration
|
||||
) async* {
|
||||
try {
|
||||
yield Right<Failure, Success>(TransformHtmlEmailContentLoading());
|
||||
final htmlContentTransformed = await emailRepository.transformHtmlEmailContent(htmlContent, configuration);
|
||||
yield Right<Failure, Success>(TransformHtmlEmailContentSuccess(htmlContentTransformed));
|
||||
} catch (e) {
|
||||
yield Left<Failure, Success>(TransformHtmlEmailContentFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -360,22 +360,33 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
}
|
||||
|
||||
void _getEmailContentAction(EmailId emailId) async {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl);
|
||||
final emailLoaded = emailSupervisorController.getEmailInQueueByEmailId(emailId);
|
||||
|
||||
if (emailLoaded != null) {
|
||||
consumeState(Stream.value(Right<Failure, Success>(
|
||||
GetEmailContentSuccess(
|
||||
emailContent: emailLoaded.originalContent,
|
||||
emailContentDisplayed: emailLoaded.displayedContent,
|
||||
htmlEmailContent: emailLoaded.htmlContent,
|
||||
attachments: emailLoaded.attachments,
|
||||
emailCurrent: emailLoaded.emailCurrent
|
||||
)
|
||||
)));
|
||||
} else if (session != null && accountId != null && baseDownloadUrl != null) {
|
||||
consumeState(_getEmailContentInteractor.execute(session, accountId, emailId, baseDownloadUrl));
|
||||
} else {
|
||||
final session = mailboxDashBoardController.sessionCurrent;
|
||||
final accountId = mailboxDashBoardController.accountId.value;
|
||||
if (session != null && accountId != null) {
|
||||
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl) ?? '';
|
||||
TransformConfiguration transformConfiguration = PlatformInfo.isWeb
|
||||
? TransformConfiguration.forPreviewEmailPlatformWeb()
|
||||
: TransformConfiguration.standardConfiguration;
|
||||
|
||||
consumeState(_getEmailContentInteractor.execute(
|
||||
session,
|
||||
accountId,
|
||||
emailId,
|
||||
baseDownloadUrl,
|
||||
transformConfiguration
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,8 +398,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
emailSupervisorController.popEmailQueue(success.emailCurrent?.id);
|
||||
|
||||
_currentEmailLoaded = EmailLoaded(
|
||||
originalContent: success.emailContent,
|
||||
displayedContent: success.emailContent,
|
||||
htmlContent: success.htmlEmailContent,
|
||||
attachments: List.of(success.attachments),
|
||||
emailCurrent: success.emailCurrent,
|
||||
);
|
||||
@@ -401,10 +411,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
_parseCalendarEventAction(
|
||||
accountId: mailboxDashBoardController.accountId.value!,
|
||||
blobIds: success.attachments.calendarEventBlobIds,
|
||||
emailContents: success.emailContent
|
||||
emailContents: success.htmlEmailContent
|
||||
);
|
||||
} else {
|
||||
emailContents.value = success.emailContent;
|
||||
emailContents.value = success.htmlEmailContent;
|
||||
}
|
||||
|
||||
final isShowMessageReadReceipt = success.emailCurrent?.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true;
|
||||
@@ -422,8 +432,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
emailSupervisorController.popEmailQueue(success.emailCurrent?.id);
|
||||
|
||||
_currentEmailLoaded = EmailLoaded(
|
||||
originalContent: success.emailContent,
|
||||
displayedContent: success.emailContentDisplayed,
|
||||
htmlContent: success.htmlEmailContent,
|
||||
attachments: List.of(success.attachments),
|
||||
emailCurrent: success.emailCurrent,
|
||||
);
|
||||
@@ -436,10 +445,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
_parseCalendarEventAction(
|
||||
accountId: mailboxDashBoardController.accountId.value!,
|
||||
blobIds: success.attachments.calendarEventBlobIds,
|
||||
emailContents: success.emailContent
|
||||
emailContents: success.htmlEmailContent
|
||||
);
|
||||
} else {
|
||||
emailContents.value = success.emailContentDisplayed;
|
||||
emailContents.value = success.htmlEmailContent;
|
||||
}
|
||||
|
||||
if (PlatformInfo.isMobile) {
|
||||
@@ -449,7 +458,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
attachments: success.attachments,
|
||||
headers: currentEmail?.emailHeader?.toSet(),
|
||||
keywords: currentEmail?.keywords,
|
||||
htmlEmailContent: success.emailContentDisplayed
|
||||
htmlEmailContent: success.htmlEmailContent
|
||||
);
|
||||
|
||||
_storeOpenedEmailAction(
|
||||
@@ -978,13 +987,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
|
||||
void composeEmailFromEmailAddress(EmailAddress emailAddress) {
|
||||
popBack();
|
||||
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromEmailAddress,
|
||||
emailAddress: emailAddress,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
|
||||
|
||||
mailboxDashBoardController.goToComposer(arguments);
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments.fromEmailAddress(emailAddress));
|
||||
}
|
||||
|
||||
void openMailToLink(Uri? uri) {
|
||||
@@ -993,12 +996,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
log('SingleEmailController::openMailToLink(): address: $address');
|
||||
if (address.isNotEmpty) {
|
||||
final emailAddress = EmailAddress(null, address);
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromEmailAddress,
|
||||
emailAddress: emailAddress,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
|
||||
|
||||
mailboxDashBoardController.goToComposer(arguments);
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments.fromEmailAddress(emailAddress));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1158,19 +1156,43 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
}
|
||||
}
|
||||
|
||||
void pressEmailAction(EmailActionType emailActionType) {
|
||||
if (emailActionType == EmailActionType.compose) {
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments());
|
||||
} else {
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: emailActionType,
|
||||
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
|
||||
emailContents: _currentEmailLoaded?.originalContent,
|
||||
attachments: emailActionType == EmailActionType.forward ? attachments : null,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role
|
||||
);
|
||||
|
||||
mailboxDashBoardController.goToComposer(arguments);
|
||||
void pressEmailAction(
|
||||
EmailActionType emailActionType,
|
||||
PresentationEmail presentationEmail
|
||||
) {
|
||||
switch(emailActionType) {
|
||||
case EmailActionType.compose:
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments());
|
||||
break;
|
||||
case EmailActionType.reply:
|
||||
mailboxDashBoardController.goToComposer(
|
||||
ComposerArguments.replyEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: _currentEmailLoaded?.htmlContent ?? '',
|
||||
mailboxRole: presentationEmail.mailboxContain?.role
|
||||
)
|
||||
);
|
||||
break;
|
||||
case EmailActionType.replyAll:
|
||||
mailboxDashBoardController.goToComposer(
|
||||
ComposerArguments.replyAllEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: _currentEmailLoaded?.htmlContent ?? '',
|
||||
mailboxRole: presentationEmail.mailboxContain?.role
|
||||
)
|
||||
);
|
||||
break;
|
||||
case EmailActionType.forward:
|
||||
mailboxDashBoardController.goToComposer(
|
||||
ComposerArguments.forwardEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: _currentEmailLoaded?.htmlContent ?? '',
|
||||
attachments: attachments
|
||||
)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1276,7 +1298,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
|
||||
void _handleParseCalendarEventFailure(ParseCalendarEventFailure failure) {
|
||||
emailLoadedViewState.value = Left<Failure, Success>(failure);
|
||||
emailContents.value = _currentEmailLoaded?.displayedContent;
|
||||
emailContents.value = _currentEmailLoaded?.htmlContent;
|
||||
}
|
||||
|
||||
void _enableScrollPageView() {
|
||||
@@ -1289,11 +1311,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
|
||||
void openNewComposerAction(String mailTo) {
|
||||
final emailAddress = EmailAddress(mailTo, mailTo);
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromEmailAddress,
|
||||
emailAddress: emailAddress,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role
|
||||
);
|
||||
mailboxDashBoardController.goToComposer(arguments);
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments.fromEmailAddress(emailAddress));
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/data/model/composer_cache.dart';
|
||||
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
|
||||
import 'package:tmail_ui_user/main/routes/router_arguments.dart';
|
||||
|
||||
@@ -26,6 +27,84 @@ class ComposerArguments extends RouterArguments {
|
||||
this.sendingEmail
|
||||
});
|
||||
|
||||
factory ComposerArguments.fromSendingEmail(SendingEmail sendingEmail) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.editSendingEmail,
|
||||
sendingEmail: sendingEmail
|
||||
);
|
||||
|
||||
factory ComposerArguments.fromContentShared(String content) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromContentShared,
|
||||
emailContents: content
|
||||
);
|
||||
|
||||
factory ComposerArguments.fromFileShared(List<SharedMediaFile> filesShared) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromFileShared,
|
||||
listSharedMediaFile: filesShared
|
||||
);
|
||||
|
||||
factory ComposerArguments.fromEmailAddress(EmailAddress emailAddress) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromEmailAddress,
|
||||
emailAddress: emailAddress
|
||||
);
|
||||
|
||||
factory ComposerArguments.editDraftEmail(PresentationEmail presentationEmail) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.editDraft,
|
||||
presentationEmail: presentationEmail
|
||||
);
|
||||
|
||||
factory ComposerArguments.fromSessionStorageBrowser(ComposerCache composerCache) =>
|
||||
ComposerArguments(
|
||||
emailActionType: EmailActionType.reopenComposerBrowser,
|
||||
presentationEmail: PresentationEmail(
|
||||
id: composerCache.id,
|
||||
subject: composerCache.subject,
|
||||
from: composerCache.from,
|
||||
to: composerCache.to,
|
||||
cc: composerCache.cc,
|
||||
bcc: composerCache.bcc,
|
||||
),
|
||||
emailContents: composerCache.emailContentList.asHtmlString,
|
||||
);
|
||||
|
||||
factory ComposerArguments.replyEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required String content,
|
||||
Role? mailboxRole,
|
||||
}) => ComposerArguments(
|
||||
emailActionType: EmailActionType.reply,
|
||||
presentationEmail: presentationEmail,
|
||||
emailContents: content,
|
||||
mailboxRole: mailboxRole
|
||||
);
|
||||
|
||||
factory ComposerArguments.replyAllEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required String content,
|
||||
Role? mailboxRole,
|
||||
}) => ComposerArguments(
|
||||
emailActionType: EmailActionType.replyAll,
|
||||
presentationEmail: presentationEmail,
|
||||
emailContents: content,
|
||||
mailboxRole: mailboxRole
|
||||
);
|
||||
|
||||
factory ComposerArguments.forwardEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required String content,
|
||||
required List<Attachment> attachments,
|
||||
}) => ComposerArguments(
|
||||
emailActionType: EmailActionType.forward,
|
||||
presentationEmail: presentationEmail,
|
||||
emailContents: content,
|
||||
attachments: attachments,
|
||||
mailboxRole: presentationEmail.mailboxContain?.role
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
emailActionType,
|
||||
|
||||
@@ -3,22 +3,19 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
|
||||
class EmailLoaded with EquatableMixin {
|
||||
final String originalContent;
|
||||
final String displayedContent;
|
||||
final String htmlContent;
|
||||
final List<Attachment> attachments;
|
||||
final Email? emailCurrent;
|
||||
|
||||
EmailLoaded({
|
||||
required this.originalContent,
|
||||
required this.displayedContent,
|
||||
required this.htmlContent,
|
||||
required this.attachments,
|
||||
this.emailCurrent,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
originalContent,
|
||||
displayedContent,
|
||||
htmlContent,
|
||||
attachments,
|
||||
emailCurrent
|
||||
];
|
||||
|
||||
@@ -9,7 +9,10 @@ import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
|
||||
typedef OnPressEmailActionClick = void Function(EmailActionType emailActionType);
|
||||
typedef OnPressEmailActionClick = void Function(
|
||||
EmailActionType emailActionType,
|
||||
PresentationEmail presentationEmail
|
||||
);
|
||||
|
||||
class BottomBarMailWidgetBuilder extends StatelessWidget {
|
||||
|
||||
@@ -60,7 +63,7 @@ class BottomBarMailWidgetBuilder extends StatelessWidget {
|
||||
color: AppColor.colorTextButton
|
||||
),
|
||||
verticalDirection: responsiveUtils.isPortraitMobile(context),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.replyAll),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.replyAll, _presentationEmail),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -79,7 +82,7 @@ class BottomBarMailWidgetBuilder extends StatelessWidget {
|
||||
color: AppColor.colorTextButton
|
||||
),
|
||||
verticalDirection: responsiveUtils.isPortraitMobile(context),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.reply),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.reply, _presentationEmail),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -98,7 +101,7 @@ class BottomBarMailWidgetBuilder extends StatelessWidget {
|
||||
color: AppColor.colorTextButton
|
||||
),
|
||||
verticalDirection: responsiveUtils.isPortraitMobile(context),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.forward),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.forward, _presentationEmail),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
@@ -117,7 +120,7 @@ class BottomBarMailWidgetBuilder extends StatelessWidget {
|
||||
color: AppColor.colorTextButton
|
||||
),
|
||||
verticalDirection: responsiveUtils.isPortraitMobile(context),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.compose),
|
||||
onTapActionCallback: () => onPressEmailActionClick?.call(EmailActionType.compose, _presentationEmail),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
||||
+6
-10
@@ -1,4 +1,5 @@
|
||||
import 'dart:convert';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:core/core.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
|
||||
import 'package:model/model.dart';
|
||||
@@ -11,13 +12,9 @@ class SessionStorageComposerDatasourceImpl
|
||||
@override
|
||||
ComposerCache getComposerCacheOnWeb() {
|
||||
try {
|
||||
final result = html.window.sessionStorage.entries
|
||||
.where((e) => e.key == EmailActionType.edit.name)
|
||||
.toList();
|
||||
if (result.isNotEmpty) {
|
||||
final jsonHandle =
|
||||
json.decode(result.first.value) as Map<String, dynamic>;
|
||||
final emailCache = ComposerCache.fromJson(jsonHandle);
|
||||
final result = html.window.sessionStorage.entries.firstWhereOrNull((e) => e.key == EmailActionType.reopenComposerBrowser.name);
|
||||
if (result != null) {
|
||||
final emailCache = ComposerCache.fromJson(jsonDecode(result.value));
|
||||
return emailCache;
|
||||
} else {
|
||||
throw NotFoundInWebSessionException();
|
||||
@@ -30,8 +27,7 @@ class SessionStorageComposerDatasourceImpl
|
||||
@override
|
||||
void removeComposerCacheOnWeb() {
|
||||
try {
|
||||
html.window.sessionStorage
|
||||
.removeWhere((key, value) => key == EmailActionType.edit.name);
|
||||
html.window.sessionStorage.removeWhere((key, value) => key == EmailActionType.reopenComposerBrowser.name);
|
||||
} catch (e) {
|
||||
throw NotFoundInWebSessionException(errorMessage: e.toString());
|
||||
}
|
||||
@@ -41,7 +37,7 @@ class SessionStorageComposerDatasourceImpl
|
||||
void saveComposerCacheOnWeb(Email email) {
|
||||
try {
|
||||
Map<String, String> entries = {
|
||||
EmailActionType.edit.name: json.encode(email.toJson())
|
||||
EmailActionType.reopenComposerBrowser.name: email.asString()
|
||||
};
|
||||
html.window.sessionStorage.addAll(entries);
|
||||
} catch (e) {
|
||||
|
||||
+25
-49
@@ -204,7 +204,8 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
ComposerArguments? composerArguments;
|
||||
NavigationRouter? navigationRouter;
|
||||
|
||||
late StreamSubscription _emailReceiveManagerStreamSubscription;
|
||||
late StreamSubscription _emailAddressStreamSubscription;
|
||||
late StreamSubscription _emailContentStreamSubscription;
|
||||
late StreamSubscription _fileReceiveManagerStreamSubscription;
|
||||
|
||||
final StreamController<Either<Failure, Success>> _progressStateController =
|
||||
@@ -267,19 +268,7 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
(failure) {},
|
||||
(success) {
|
||||
if(success is GetComposerCacheSuccess){
|
||||
final ComposerArguments composerArguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.edit,
|
||||
presentationEmail: PresentationEmail(
|
||||
id: success.composerCache.id,
|
||||
subject: success.composerCache.subject,
|
||||
from: success.composerCache.from,
|
||||
to: success.composerCache.to,
|
||||
cc: success.composerCache.cc,
|
||||
bcc: success.composerCache.bcc,
|
||||
),
|
||||
emailContents: success.composerCache.emailContentList.asHtmlString,
|
||||
);
|
||||
openComposerOverlay(composerArguments);
|
||||
openComposerOverlay(ComposerArguments.fromSessionStorageBrowser(success.composerCache));
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -373,49 +362,33 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
}
|
||||
|
||||
void _registerPendingEmailAddress() {
|
||||
_emailReceiveManagerStreamSubscription =
|
||||
_emailReceiveManager.pendingEmailAddressInfo.stream.listen((emailAddress) {
|
||||
log('MailboxDashBoardController::_registerPendingEmailAddress(): ${emailAddress?.email}');
|
||||
if (emailAddress != null && emailAddress.email?.isNotEmpty == true) {
|
||||
_emailReceiveManager.clearPendingEmailAddress();
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.composeFromEmailAddress,
|
||||
emailAddress: emailAddress,
|
||||
mailboxRole: selectedMailbox.value?.role);
|
||||
goToComposer(arguments);
|
||||
}
|
||||
});
|
||||
_emailAddressStreamSubscription =
|
||||
_emailReceiveManager.pendingEmailAddressInfo.stream.listen((emailAddress) {
|
||||
if (emailAddress?.email?.isNotEmpty == true) {
|
||||
_emailReceiveManager.clearPendingEmailAddress();
|
||||
goToComposer(ComposerArguments.fromEmailAddress(emailAddress!));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _registerPendingEmailContents() {
|
||||
_emailReceiveManagerStreamSubscription =
|
||||
_emailContentStreamSubscription =
|
||||
_emailReceiveManager.pendingEmailContentInfo.stream.listen((emailContent) {
|
||||
log('MailboxDashBoardController::_registerPendingEmailContents(): ${emailContent?.content}');
|
||||
if (emailContent != null && emailContent.content.isNotEmpty == true) {
|
||||
if (emailContent?.content.isNotEmpty == true) {
|
||||
_emailReceiveManager.clearPendingEmailContent();
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.edit,
|
||||
emailContents: [emailContent].asHtmlString,
|
||||
mailboxRole: selectedMailbox.value?.role);
|
||||
goToComposer(arguments);
|
||||
}
|
||||
});
|
||||
goToComposer(ComposerArguments.fromContentShared([emailContent!].asHtmlString));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _registerPendingFileInfo() {
|
||||
_fileReceiveManagerStreamSubscription =
|
||||
_emailReceiveManager.pendingFileInfo.stream.listen((listFile) {
|
||||
log('MailboxDashBoardController::_registerPendingFileInfo(): ${listFile.length}');
|
||||
if (listFile.isNotEmpty && sessionCurrent != null) {
|
||||
_emailReceiveManager.clearPendingFileInfo();
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.edit,
|
||||
mailboxRole: selectedMailbox.value?.role,
|
||||
listSharedMediaFile: listFile,
|
||||
);
|
||||
goToComposer(arguments);
|
||||
}
|
||||
});
|
||||
_emailReceiveManager.pendingFileInfo.stream.listen((listFile) {
|
||||
if (listFile.isNotEmpty) {
|
||||
_emailReceiveManager.clearPendingFileInfo();
|
||||
goToComposer(ComposerArguments.fromFileShared(listFile));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _registerStreamListener() {
|
||||
@@ -2071,11 +2044,14 @@ class MailboxDashBoardController extends ReloadableController {
|
||||
void selectAllEmailAction() {
|
||||
dispatchAction(SelectionAllEmailAction());
|
||||
}
|
||||
|
||||
String get baseDownloadUrl => sessionCurrent?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl) ?? '';
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
_emailReceiveManager.closeEmailReceiveManagerStream();
|
||||
_emailReceiveManagerStreamSubscription.cancel();
|
||||
_emailAddressStreamSubscription.cancel();
|
||||
_emailContentStreamSubscription.cancel();
|
||||
_fileReceiveManagerStreamSubscription.cancel();
|
||||
_progressStateController.close();
|
||||
_refreshActionEventController.close();
|
||||
|
||||
@@ -62,7 +62,7 @@ class IdentityDataSourceImpl extends IdentityDataSource {
|
||||
Future<String> transformHtmlSignature(String signature) {
|
||||
return Future.sync(() async {
|
||||
final signatureUnescape = await _htmlTransform.transformToHtml(
|
||||
contentHtml: signature,
|
||||
htmlContent: signature,
|
||||
transformConfiguration: TransformConfiguration.create(customDomTransformers: [
|
||||
const RemoveScriptTransformer(),
|
||||
const BlockQuotedTransformer(),
|
||||
|
||||
@@ -664,7 +664,7 @@ class SearchEmailController extends BaseController
|
||||
switch(actionType) {
|
||||
case EmailActionType.preview:
|
||||
if (mailboxContain?.isDrafts == true) {
|
||||
editEmail(selectedEmail);
|
||||
editDraftEmail(selectedEmail);
|
||||
} else {
|
||||
previewEmail(selectedEmail);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import 'package:get/get.dart';
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/user_name.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/extensions/list_email_content_extension.dart';
|
||||
import 'package:model/mailbox/select_mode.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_controller.dart';
|
||||
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart';
|
||||
@@ -270,16 +268,7 @@ class SendingQueueController extends BaseController with MessageDialogActionMixi
|
||||
|
||||
void _editSendingEmailAction(SendingEmail sendingEmail) {
|
||||
disableSelectionMode();
|
||||
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.edit,
|
||||
presentationEmail: sendingEmail.presentationEmail,
|
||||
mailboxRole: dashboardController?.selectedMailbox.value?.role,
|
||||
emailContents: sendingEmail.presentationEmail.emailContentList.asHtmlString,
|
||||
sendingEmail: sendingEmail
|
||||
);
|
||||
|
||||
dashboardController?.goToComposer(arguments);
|
||||
dashboardController?.goToComposer(ComposerArguments.fromSendingEmail(sendingEmail));
|
||||
}
|
||||
|
||||
void _resendSendingEmailAction(List<SendingEmail> listSendingEmails) async {
|
||||
|
||||
@@ -37,13 +37,8 @@ mixin EmailActionController {
|
||||
final responsiveUtils = Get.find<ResponsiveUtils>();
|
||||
final imagePaths = Get.find<ImagePaths>();
|
||||
|
||||
void editEmail(PresentationEmail presentationEmail) {
|
||||
final arguments = ComposerArguments(
|
||||
emailActionType: EmailActionType.edit,
|
||||
presentationEmail: presentationEmail,
|
||||
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role);
|
||||
|
||||
mailboxDashBoardController.goToComposer(arguments);
|
||||
void editDraftEmail(PresentationEmail presentationEmail) {
|
||||
mailboxDashBoardController.goToComposer(ComposerArguments.editDraftEmail(presentationEmail));
|
||||
}
|
||||
|
||||
void previewEmail(PresentationEmail presentationEmail) {
|
||||
|
||||
@@ -873,7 +873,7 @@ class ThreadController extends BaseController with EmailActionController {
|
||||
switch(actionType) {
|
||||
case EmailActionType.preview:
|
||||
if (mailboxContain?.isDrafts == true) {
|
||||
editEmail(selectedEmail);
|
||||
editDraftEmail(selectedEmail);
|
||||
} else {
|
||||
previewEmail(selectedEmail);
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ class NetworkBindings extends Bindings {
|
||||
|
||||
void _bindingTransformer() {
|
||||
Get.put(const HtmlEscape());
|
||||
Get.put(HtmlTransform(Get.find<DioClient>()));
|
||||
Get.put(HtmlAnalyzer(Get.find<HtmlTransform>(), Get.find<HtmlEscape>()));
|
||||
Get.put(HtmlTransform(Get.find<DioClient>(), Get.find<HtmlEscape>()));
|
||||
Get.put(HtmlAnalyzer(Get.find<HtmlTransform>()));
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,12 @@ enum EmailActionType {
|
||||
markAsStarred,
|
||||
unMarkAsStarred,
|
||||
moveToMailbox,
|
||||
edit,
|
||||
editDraft,
|
||||
editSendingEmail,
|
||||
composeFromContentShared,
|
||||
composeFromFileShared,
|
||||
composeFromEmailAddress,
|
||||
reopenComposerBrowser,
|
||||
moveToTrash,
|
||||
deletePermanently,
|
||||
preview,
|
||||
|
||||
@@ -19,6 +19,8 @@ extension EmailExtension on Email {
|
||||
|
||||
bool get hasMdnSent => keywords?.containsKey(KeyWordIdentifier.mdnSent) == true;
|
||||
|
||||
bool get isDraft => keywords?.containsKey(KeyWordIdentifier.emailDraft) == true;
|
||||
|
||||
bool get withAttachments => hasAttachment == true;
|
||||
|
||||
bool hasReadReceipt(Map<MailboxId, PresentationMailbox> mapMailbox) {
|
||||
@@ -127,6 +129,13 @@ extension EmailExtension on Email {
|
||||
return [];
|
||||
}
|
||||
|
||||
List<Attachment> get attachmentsWithCid {
|
||||
return attachments
|
||||
?.where((element) => element.disposition != null && element.cid?.isNotEmpty == true)
|
||||
.map((item) => item.toAttachment())
|
||||
.toList() ?? [];
|
||||
}
|
||||
|
||||
PresentationMailbox? findMailboxContain(Map<MailboxId, PresentationMailbox> mapMailbox) {
|
||||
final newMailboxIds = mailboxIds;
|
||||
newMailboxIds?.removeWhere((key, value) => !value);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:model/model.dart';
|
||||
|
||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:model/email/attachment.dart';
|
||||
|
||||
extension ListAttachmentExtension on List<Attachment> {
|
||||
|
||||
@@ -20,4 +22,15 @@ extension ListAttachmentExtension on List<Attachment> {
|
||||
return where((attachment) => attachment.hasCid())
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<String, String> toMapCidImageDownloadUrl({
|
||||
required AccountId accountId,
|
||||
required String downloadUrl
|
||||
}) {
|
||||
final mapUrlDownloadCID = {
|
||||
for (var attachment in listAttachmentsDisplayedInContent)
|
||||
attachment.cid! : attachment.getDownloadUrl(downloadUrl, accountId)
|
||||
};
|
||||
return mapUrlDownloadCID;
|
||||
}
|
||||
}
|
||||
@@ -29,9 +29,9 @@ extension ListEmailAddressExtension on Set<EmailAddress>? {
|
||||
|
||||
int numberEmailAddress() => this != null ? this!.length : 0;
|
||||
|
||||
List<EmailAddress> filterEmailAddress(EmailAddress emailAddressNotExist) {
|
||||
List<EmailAddress> filterEmailAddress(String emailAddressNotExist) {
|
||||
return this != null
|
||||
? this!.where((emailAddress) => emailAddress.email != emailAddressNotExist.email).toList()
|
||||
? this!.where((emailAddress) => emailAddress.email != emailAddressNotExist).toList()
|
||||
: List.empty();
|
||||
}
|
||||
}
|
||||
@@ -110,10 +110,10 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
return allEmailAddress.isNotEmpty ? allEmailAddress.join(', ') : '';
|
||||
}
|
||||
|
||||
Tuple3<List<EmailAddress>, List<EmailAddress>, List<EmailAddress>> generateRecipientsEmailAddressForComposer(
|
||||
EmailActionType? emailActionType,
|
||||
Role? mailboxRole
|
||||
) {
|
||||
Tuple3<List<EmailAddress>, List<EmailAddress>, List<EmailAddress>> generateRecipientsEmailAddressForComposer({
|
||||
required EmailActionType emailActionType,
|
||||
Role? mailboxRole
|
||||
}) {
|
||||
switch(emailActionType) {
|
||||
case EmailActionType.reply:
|
||||
if (mailboxRole == PresentationMailbox.roleSent) {
|
||||
@@ -128,10 +128,8 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
} else {
|
||||
return Tuple3(to.asList() + from.asList(), cc.asList(), bcc.asList());
|
||||
}
|
||||
case EmailActionType.edit:
|
||||
return Tuple3(to.asList(), cc.asList(), bcc.asList());
|
||||
default:
|
||||
return const Tuple3([], [], []);
|
||||
return Tuple3(to.asList(), cc.asList(), bcc.asList());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user