Skip to content

Commit

Permalink
TF-3413 Fix ReplyAll & Reply is buggy
Browse files Browse the repository at this point in the history
  • Loading branch information
dab246 authored and hoangdat committed Jan 9, 2025
1 parent f2b6bb2 commit 2816c2a
Show file tree
Hide file tree
Showing 10 changed files with 407 additions and 142 deletions.
34 changes: 11 additions & 23 deletions lib/features/composer/presentation/composer_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
Expand Down Expand Up @@ -647,8 +646,6 @@ class ComposerController extends BaseController
_initEmailAddress(
presentationEmail: arguments.presentationEmail!,
actionType: arguments.emailActionType,
mailboxRole: arguments.presentationEmail!.mailboxContain?.role
?? mailboxDashBoardController.selectedMailbox.value?.role,
listPost: arguments.listPost,
);
_initSubjectEmail(
Expand Down Expand Up @@ -802,32 +799,23 @@ class ComposerController extends BaseController
void _initEmailAddress({
required PresentationEmail presentationEmail,
required EmailActionType actionType,
Role? mailboxRole,
String? listPost,
}) {
log('ComposerController::_initEmailAddress:listPost = $listPost');
final userName = mailboxDashBoardController.sessionCurrent?.username.value;
final isSender = presentationEmail.from
.asList()
.any((element) => element.emailAddress.isNotEmpty && element.emailAddress == userName);

final recipients = presentationEmail.generateRecipientsEmailAddressForComposer(
emailActionType: actionType,
mailboxRole: mailboxRole,
isSender: isSender,
userName: userName,
listPost: listPost,
);
final userName = mailboxDashBoardController.sessionCurrent?.username;
if (userName != null) {
final isSender = presentationEmail.from.asList().every((element) => element.email == userName.value);
if (isSender) {
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(userName.value));
listCcEmailAddress = List.from(recipients.value2.toSet().filterEmailAddress(userName.value));
listBccEmailAddress = List.from(recipients.value3.toSet().filterEmailAddress(userName.value));
}
} else {
listToEmailAddress = List.from(recipients.value1.toSet());
listCcEmailAddress = List.from(recipients.value2.toSet());
listBccEmailAddress = List.from(recipients.value3.toSet());
}

listToEmailAddress = List.from(recipients.value1);
listCcEmailAddress = List.from(recipients.value2);
listBccEmailAddress = List.from(recipients.value3);

if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) {
isInitialRecipient.value = true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
references: currentEmailLoaded.value?.emailCurrent?.references,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
)
);
break;
Expand All @@ -1448,6 +1449,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
references: currentEmailLoaded.value?.emailCurrent?.references,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
)
);
break;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,91 @@

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,
bool isSender = false,
String? userName,
String? listPost,
}) {
final newFromAddress = from.removeDuplicateEmails();
final newToAddress = to.removeDuplicateEmails();
final newCcAddress = cc.removeDuplicateEmails();
final newBccAddress = bcc.removeDuplicateEmails();
final newReplyToAddress = replyTo.removeDuplicateEmails();

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, [], []);
}
final listReplyAddress = isSender ? newToAddress : newFromAddress;
final listReplyAddressWithoutUsername = listReplyAddress.withoutMe(userName);

return Tuple3(listReplyAddressWithoutUsername, [], []);
case EmailActionType.replyToList:
final listEmailAddress = EmailUtils.parsingListPost(listPost ?? '') ?? [];
log('PresentationEmailExtension::generateRecipientsEmailAddressForComposer:listEmailAddress = $listEmailAddress');
return Tuple3(listEmailAddress, [], []);
final recipientRecord = EmailUtils.extractRecipientsFromListPost(listPost ?? '');

final listToAddressWithoutUsername = recipientRecord.toMailAddresses
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);

final listCcAddressWithoutUsername = recipientRecord.ccMailAddresses
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);

final listBccAddressWithoutUsername = recipientRecord.bccMailAddresses
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);

return Tuple3(
listToAddressWithoutUsername,
listCcAddressWithoutUsername,
listBccAddressWithoutUsername,
);
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(),
);
}
final recipientRecord = EmailUtils.extractRecipientsFromListPost(listPost ?? '');

final listToAddress = recipientRecord.toMailAddresses
+ newReplyToAddress
+ newFromAddress
+ newToAddress;
final listCcAddress = recipientRecord.ccMailAddresses + newCcAddress;
final listBccAddress = recipientRecord.bccMailAddresses + newBccAddress;

final listToAddressWithoutUsername = listToAddress
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);
final listCcAddressWithoutUsername = listCcAddress
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);
final listBccAddressWithoutUsername = listBccAddress
.toSet()
.removeDuplicateEmails()
.withoutMe(userName);

return Tuple3(
listToAddressWithoutUsername,
listCcAddressWithoutUsername,
listBccAddressWithoutUsername,
);
default:
return Tuple3(to.asList(), cc.asList(), bcc.asList());
final listToAddressWithoutUsername = newToAddress.withoutMe(userName);
final listCcAddressWithoutUsername = newCcAddress.withoutMe(userName);
final listBccAddressWithoutUsername = newBccAddress.withoutMe(userName);

return Tuple3(
listToAddressWithoutUsername,
listCcAddressWithoutUsername,
listBccAddressWithoutUsername,
);
}
}
}
4 changes: 4 additions & 0 deletions lib/features/email/presentation/model/composer_arguments.dart
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class ComposerArguments extends RouterArguments {
Role? mailboxRole,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
String? listPost,
}) => ComposerArguments(
emailActionType: EmailActionType.reply,
presentationEmail: presentationEmail,
Expand All @@ -130,6 +131,7 @@ class ComposerArguments extends RouterArguments {
mailboxRole: mailboxRole,
messageId: messageId,
references: references,
listPost: listPost,
);

factory ComposerArguments.replyToListEmail({
Expand Down Expand Up @@ -158,6 +160,7 @@ class ComposerArguments extends RouterArguments {
Role? mailboxRole,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
String? listPost,
}) => ComposerArguments(
emailActionType: EmailActionType.replyAll,
presentationEmail: presentationEmail,
Expand All @@ -166,6 +169,7 @@ class ComposerArguments extends RouterArguments {
mailboxRole: mailboxRole,
messageId: messageId,
references: references,
listPost: listPost,
);

factory ComposerArguments.forwardEmail({
Expand Down
123 changes: 97 additions & 26 deletions lib/features/email/presentation/utils/email_utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,37 +86,108 @@ class EmailUtils {
}
}

static List<EmailAddress>? parsingListPost(String listPost) {
static List<String> extractMailtoLinksFromListPost(String listPost) {
try {
if (listPost.isEmpty) {
return null;
if (listPost.trim().isEmpty) return [];

final decodedInput = Uri.decodeComponent(listPost);

final mailtoRegex = RegExp(r'<(mailto:[^<>]+)>');

final matches = mailtoRegex.allMatches(decodedInput);

if (matches.isEmpty) {
log('EmailUtils::extractMailtoLinksFromListPost: Not found mailto link');
return [];
}

return matches.map((match) => match.group(1)!).toList();
} catch (e) {
logError('EmailUtils::extractMailtoLinksFromListPost:Exception = $e');
return [];
}
}

static ({
List<EmailAddress> toMailAddresses,
List<EmailAddress> ccMailAddresses,
List<EmailAddress> bccMailAddresses,
}) extractRecipientsFromListMailtoLink(List<String> mailtoLinks) {
try {
log('EmailUtils::extractRecipientsFromListMailtoLink: mailtoLinks: $mailtoLinks:');
if (mailtoLinks.isEmpty) {
return (
toMailAddresses: [],
ccMailAddresses: [],
bccMailAddresses: [],
);
}

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;
final toMailAddresses = <EmailAddress>[];
final ccMailAddresses = <EmailAddress>[];
final bccMailAddresses = <EmailAddress>[];

for (var mailtoLink in mailtoLinks) {
final recipientRecord = extractRecipientsFromMailtoLink(mailtoLink);
toMailAddresses.addAll(recipientRecord.toMailAddresses);
ccMailAddresses.addAll(recipientRecord.ccMailAddresses);
bccMailAddresses.addAll(recipientRecord.bccMailAddresses);
}

return (
toMailAddresses: toMailAddresses,
ccMailAddresses: ccMailAddresses,
bccMailAddresses: bccMailAddresses
);
} catch (e) {
logError('EmailUtils::parsingListPost:Exception = $e');
return null;
logError('EmailUtils::extractRecipientsFromListMailtoLink:Exception = $e');
return (
toMailAddresses: [],
ccMailAddresses: [],
bccMailAddresses: [],
);
}
}

static ({
List<EmailAddress> toMailAddresses,
List<EmailAddress> ccMailAddresses,
List<EmailAddress> bccMailAddresses,
}) extractRecipientsFromMailtoLink(String mailtoLink) {
try {
log('EmailUtils::extractRecipientsFromMailtoLink:mailtoLink: $mailtoLink:');
if (mailtoLink.isEmpty) {
return (
toMailAddresses: [],
ccMailAddresses: [],
bccMailAddresses: [],
);
}

final navigationRouter =
RouteUtils.generateNavigationRouterFromMailtoLink(mailtoLink);
log('EmailUtils::extractRecipientsFromMailtoLink:navigationRouter = $navigationRouter');
return (
toMailAddresses: navigationRouter.listEmailAddress ?? [],
ccMailAddresses: navigationRouter.cc ?? [],
bccMailAddresses: navigationRouter.bcc ?? [],
);
} catch (e) {
logError('EmailUtils::extractRecipientsFromMailtoLink:Exception = $e');
return (
toMailAddresses: [],
ccMailAddresses: [],
bccMailAddresses: [],
);
}
}

static ({
List<EmailAddress> toMailAddresses,
List<EmailAddress> ccMailAddresses,
List<EmailAddress> bccMailAddresses,
}) extractRecipientsFromListPost(String listPost) {
final mailtoLinks = extractMailtoLinksFromListPost(listPost);
return extractRecipientsFromListMailtoLink(mailtoLinks);
}
}
19 changes: 19 additions & 0 deletions model/lib/extensions/list_email_address_extension.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,27 @@ extension SetEmailAddressExtension on Set<EmailAddress>? {
Set<EmailAddress> withoutMe(String userName) {
return filterEmailAddress(userName).toSet();
}

List<EmailAddress> removeDuplicateEmails() {
final seenEmails = <String>{};
return this?.where((emailAddress) {
if (emailAddress.emailAddress.isEmpty ||
seenEmails.contains(emailAddress.emailAddress)) {
return false;
} else {
seenEmails.add(emailAddress.emailAddress);
return true;
}
}).toList() ?? [];
}
}

extension ListEmailAddressExtension on List<EmailAddress> {
Set<String> asSetAddress() => map((emailAddress) => emailAddress.emailAddress).toSet();

List<EmailAddress> withoutMe(String? userName) {
if (userName == null) return this;

return where((emailAddress) => emailAddress.emailAddress != userName).toList();
}
}
Loading

0 comments on commit 2816c2a

Please sign in to comment.