TF-3413 Support Reply to list for email

This commit is contained in:
dab246
2025-01-09 05:04:55 +07:00
committed by Dat H. Pham
parent 1689f709b5
commit 77b756cbb6
20 changed files with 277 additions and 32 deletions
@@ -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(
+7 -1
View File
@@ -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',
);
}
}