TF-3413 Support Reply to list for email
This commit is contained in:
@@ -40,7 +40,9 @@ class EmailRequest with EquatableMixin {
|
||||
];
|
||||
|
||||
bool get isEmailAnswered => emailIdAnsweredOrForwarded != null &&
|
||||
(emailActionType == EmailActionType.reply || emailActionType == EmailActionType.replyAll);
|
||||
(emailActionType == EmailActionType.reply
|
||||
|| emailActionType == EmailActionType.replyToList
|
||||
|| emailActionType == EmailActionType.replyAll);
|
||||
|
||||
bool get isEmailForwarded => emailIdAnsweredOrForwarded != null && emailActionType == EmailActionType.forward;
|
||||
}
|
||||
@@ -74,6 +74,7 @@ import 'package:tmail_ui_user/features/email/domain/state/get_email_content_stat
|
||||
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/extensions/presentation_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
|
||||
@@ -640,11 +641,15 @@ class ComposerController extends BaseController
|
||||
_updateStatusEmailSendButton();
|
||||
break;
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyToList:
|
||||
case EmailActionType.replyAll:
|
||||
log('ComposerController::_initEmail:listPost = ${arguments.listPost}');
|
||||
_initEmailAddress(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
actionType: arguments.emailActionType,
|
||||
mailboxRole: arguments.presentationEmail!.mailboxContain?.role ?? mailboxDashBoardController.selectedMailbox.value?.role
|
||||
mailboxRole: arguments.presentationEmail!.mailboxContain?.role
|
||||
?? mailboxDashBoardController.selectedMailbox.value?.role,
|
||||
listPost: arguments.listPost,
|
||||
);
|
||||
_initSubjectEmail(
|
||||
presentationEmail: arguments.presentationEmail!,
|
||||
@@ -798,12 +803,15 @@ class ComposerController extends BaseController
|
||||
required PresentationEmail presentationEmail,
|
||||
required EmailActionType actionType,
|
||||
Role? mailboxRole,
|
||||
String? listPost,
|
||||
}) {
|
||||
log('ComposerController::_initEmailAddress:listPost = $listPost');
|
||||
final recipients = presentationEmail.generateRecipientsEmailAddressForComposer(
|
||||
emailActionType: actionType,
|
||||
mailboxRole: mailboxRole
|
||||
mailboxRole: mailboxRole,
|
||||
listPost: listPost,
|
||||
);
|
||||
final userName = mailboxDashBoardController.sessionCurrent?.username;
|
||||
final userName = mailboxDashBoardController.sessionCurrent?.username;
|
||||
if (userName != null) {
|
||||
final isSender = presentationEmail.from.asList().every((element) => element.email == userName.value);
|
||||
if (isSender) {
|
||||
|
||||
@@ -74,6 +74,7 @@ extension CreateEmailRequestExtension on CreateEmailRequest {
|
||||
|
||||
MessageIdsHeaderValue? createInReplyTo() {
|
||||
if (emailActionType == EmailActionType.reply ||
|
||||
emailActionType == EmailActionType.replyToList ||
|
||||
emailActionType == EmailActionType.replyAll
|
||||
) {
|
||||
return messageId;
|
||||
@@ -83,6 +84,7 @@ extension CreateEmailRequestExtension on CreateEmailRequest {
|
||||
|
||||
MessageIdsHeaderValue? createReferences() {
|
||||
if (emailActionType == EmailActionType.reply ||
|
||||
emailActionType == EmailActionType.replyToList ||
|
||||
emailActionType == EmailActionType.replyAll ||
|
||||
emailActionType == EmailActionType.forward
|
||||
) {
|
||||
|
||||
@@ -12,6 +12,7 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
String getSubjectComposer(BuildContext? context, String subject) {
|
||||
switch(this) {
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyToList:
|
||||
case EmailActionType.replyAll:
|
||||
if (subject.toLowerCase().startsWith('re:')) {
|
||||
return subject;
|
||||
@@ -60,6 +61,7 @@ extension EmailActionTypeExtension on EmailActionType {
|
||||
final languageTag = locale.toLanguageTag();
|
||||
switch(this) {
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyToList:
|
||||
case EmailActionType.replyAll:
|
||||
final receivedAt = presentationEmail.receivedAt;
|
||||
final emailAddress = presentationEmail.from.toEscapeHtmlStringUseCommaSeparator();
|
||||
|
||||
@@ -80,6 +80,7 @@ class MobileEditorView extends StatelessWidget with EditorViewMixin {
|
||||
}
|
||||
);
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyToList:
|
||||
case EmailActionType.replyAll:
|
||||
case EmailActionType.forward:
|
||||
if (contentViewState == null) {
|
||||
|
||||
@@ -145,6 +145,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
||||
}
|
||||
);
|
||||
case EmailActionType.reply:
|
||||
case EmailActionType.replyToList:
|
||||
case EmailActionType.replyAll:
|
||||
case EmailActionType.forward:
|
||||
if (contentViewState == null) {
|
||||
|
||||
@@ -1425,6 +1425,20 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
||||
)
|
||||
);
|
||||
break;
|
||||
case EmailActionType.replyToList:
|
||||
log('SingleEmailController::pressEmailAction:replyToList');
|
||||
mailboxDashBoardController.goToComposer(
|
||||
ComposerArguments.replyToListEmail(
|
||||
presentationEmail: presentationEmail,
|
||||
content: currentEmailLoaded.value?.htmlContent ?? '',
|
||||
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
|
||||
mailboxRole: presentationEmail.mailboxContain?.role,
|
||||
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
|
||||
references: currentEmailLoaded.value?.emailCurrent?.references,
|
||||
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
|
||||
)
|
||||
);
|
||||
break;
|
||||
case EmailActionType.replyAll:
|
||||
mailboxDashBoardController.goToComposer(
|
||||
ComposerArguments.replyAllEmail(
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/list_email_address_extension.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
|
||||
|
||||
extension PresentationEmailExtension on PresentationEmail {
|
||||
Tuple3<List<EmailAddress>, List<EmailAddress>, List<EmailAddress>> generateRecipientsEmailAddressForComposer({
|
||||
required EmailActionType emailActionType,
|
||||
Role? mailboxRole,
|
||||
String? listPost,
|
||||
}) {
|
||||
switch (emailActionType) {
|
||||
case EmailActionType.reply:
|
||||
if (mailboxRole == PresentationMailbox.roleSent) {
|
||||
return Tuple3(to.asList(), [], []);
|
||||
} else {
|
||||
final replyToAddress = replyTo.asList().isNotEmpty
|
||||
? replyTo.asList()
|
||||
: from.asList();
|
||||
return Tuple3(replyToAddress, [], []);
|
||||
}
|
||||
case EmailActionType.replyToList:
|
||||
final listEmailAddress = EmailUtils.parsingListPost(listPost ?? '') ?? [];
|
||||
log('PresentationEmailExtension::generateRecipientsEmailAddressForComposer:listEmailAddress = $listEmailAddress');
|
||||
return Tuple3(listEmailAddress, [], []);
|
||||
case EmailActionType.replyAll:
|
||||
if (mailboxRole == PresentationMailbox.roleSent) {
|
||||
return Tuple3(to.asList(), cc.asList(), bcc.asList());
|
||||
} else {
|
||||
final senderReplyToAddress = replyTo.asList().isNotEmpty
|
||||
? replyTo.asList()
|
||||
: from.asList();
|
||||
return Tuple3(
|
||||
to.asList() + senderReplyToAddress,
|
||||
cc.asList(),
|
||||
bcc.asList(),
|
||||
);
|
||||
}
|
||||
default:
|
||||
return Tuple3(to.asList(), cc.asList(), bcc.asList());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ class ComposerArguments extends RouterArguments {
|
||||
final ScreenDisplayMode displayMode;
|
||||
final List<EmailAddress>? cc;
|
||||
final List<EmailAddress>? bcc;
|
||||
final String? listPost;
|
||||
|
||||
ComposerArguments({
|
||||
this.emailActionType = EmailActionType.compose,
|
||||
@@ -54,6 +55,7 @@ class ComposerArguments extends RouterArguments {
|
||||
this.displayMode = ScreenDisplayMode.normal,
|
||||
this.cc,
|
||||
this.bcc,
|
||||
this.listPost,
|
||||
});
|
||||
|
||||
factory ComposerArguments.fromSendingEmail(SendingEmail sendingEmail) =>
|
||||
@@ -130,6 +132,25 @@ class ComposerArguments extends RouterArguments {
|
||||
references: references,
|
||||
);
|
||||
|
||||
factory ComposerArguments.replyToListEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required String content,
|
||||
required List<Attachment> inlineImages,
|
||||
Role? mailboxRole,
|
||||
MessageIdsHeaderValue? messageId,
|
||||
MessageIdsHeaderValue? references,
|
||||
String? listPost,
|
||||
}) => ComposerArguments(
|
||||
emailActionType: EmailActionType.replyToList,
|
||||
presentationEmail: presentationEmail,
|
||||
emailContents: content,
|
||||
inlineImages: inlineImages,
|
||||
mailboxRole: mailboxRole,
|
||||
messageId: messageId,
|
||||
references: references,
|
||||
listPost: listPost,
|
||||
);
|
||||
|
||||
factory ComposerArguments.replyAllEmail({
|
||||
required PresentationEmail presentationEmail,
|
||||
required String content,
|
||||
@@ -206,6 +227,7 @@ class ComposerArguments extends RouterArguments {
|
||||
displayMode,
|
||||
cc,
|
||||
bcc,
|
||||
listPost,
|
||||
];
|
||||
|
||||
ComposerArguments copyWith({
|
||||
@@ -229,6 +251,7 @@ class ComposerArguments extends RouterArguments {
|
||||
ScreenDisplayMode? displayMode,
|
||||
List<EmailAddress>? cc,
|
||||
List<EmailAddress>? bcc,
|
||||
String? listPost,
|
||||
}) {
|
||||
return ComposerArguments(
|
||||
emailActionType: emailActionType ?? this.emailActionType,
|
||||
@@ -251,6 +274,7 @@ class ComposerArguments extends RouterArguments {
|
||||
displayMode: displayMode ?? this.displayMode,
|
||||
cc: cc ?? this.cc,
|
||||
bcc: bcc ?? this.bcc,
|
||||
listPost: listPost ?? this.listPost,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,10 +9,12 @@ import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
|
||||
import 'package:jmap_dart_client/jmap/core/session/session.dart';
|
||||
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
|
||||
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
|
||||
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
|
||||
import 'package:tmail_ui_user/main/error/capability_validator.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_utils.dart';
|
||||
|
||||
class EmailUtils {
|
||||
|
||||
@@ -83,4 +85,38 @@ class EmailUtils {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static List<EmailAddress>? parsingListPost(String listPost) {
|
||||
try {
|
||||
if (listPost.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final regExpMailtoLinks = RegExp(r'mailto:([^>,]*)');
|
||||
final allMatchesMailtoLinks = regExpMailtoLinks.allMatches(listPost);
|
||||
final listMailtoLinks = allMatchesMailtoLinks
|
||||
.map((match) => match.group(0))
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
log('EmailUtils::parsingListPost:listMailtoLinks: $listMailtoLinks');
|
||||
|
||||
if (listMailtoLinks.isNotEmpty) {
|
||||
return listMailtoLinks
|
||||
.map((mailto) {
|
||||
final mapMailto = RouteUtils.parseMapMailtoFromUri(mailto);
|
||||
final emailAddress = mapMailto[RouteUtils.paramMailtoAddress];
|
||||
return emailAddress != null
|
||||
? EmailAddress(null, emailAddress)
|
||||
: null;
|
||||
})
|
||||
.whereNotNull()
|
||||
.toList();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} catch (e) {
|
||||
logError('EmailUtils::parsingListPost:Exception = $e');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/email_extension.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_bottom_bar_widget_styles.dart';
|
||||
@@ -73,6 +74,37 @@ class EmailViewBottomBarWidget extends StatelessWidget {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
final currentEmailLoaded = _singleEmailController.currentEmailLoaded.value;
|
||||
|
||||
if (currentEmailLoaded != null &&
|
||||
currentEmailLoaded.emailCurrent?.hasListPost == true) {
|
||||
return Expanded(
|
||||
child: TMailButtonWidget(
|
||||
key: const Key('reply_to_list_email_button'),
|
||||
text: AppLocalizations.of(context).replyToList,
|
||||
icon: _imagePaths.icReply,
|
||||
borderRadius: EmailViewBottomBarWidgetStyles.buttonRadius,
|
||||
iconSize: EmailViewBottomBarWidgetStyles.buttonIconSize,
|
||||
textAlign: TextAlign.center,
|
||||
flexibleText: true,
|
||||
padding: EmailViewBottomBarWidgetStyles.buttonPadding,
|
||||
backgroundColor: EmailViewBottomBarWidgetStyles.buttonBackgroundColor,
|
||||
textStyle: EmailViewBottomBarWidgetStyles.getButtonTextStyle(
|
||||
context,
|
||||
_responsiveUtils,
|
||||
),
|
||||
verticalDirection: _responsiveUtils.isPortraitMobile(context),
|
||||
onTapActionCallback: () => emailActionCallback.call(
|
||||
EmailActionType.replyToList,
|
||||
presentationEmail,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
Obx(() {
|
||||
if (_singleEmailController.currentEmailLoaded.value != null) {
|
||||
return Expanded(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"@@last_modified": "2024-12-31T12:11:05.777668",
|
||||
"@@last_modified": "2025-01-09T04:35:09.125475",
|
||||
"initializing_data": "Initializing data...",
|
||||
"@initializing_data": {
|
||||
"type": "text",
|
||||
@@ -4083,5 +4083,11 @@
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
},
|
||||
"replyToList": "Reply to list",
|
||||
"@replyToList": {
|
||||
"type": "text",
|
||||
"placeholders_order": [],
|
||||
"placeholders": {}
|
||||
}
|
||||
}
|
||||
@@ -4286,4 +4286,12 @@ class AppLocalizations {
|
||||
name: 'getHelpOrReportABug',
|
||||
);
|
||||
}
|
||||
|
||||
String get replyToList {
|
||||
return Intl.message(
|
||||
'Reply to list',
|
||||
name: 'replyToList',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user