TF-1718 Implement unsubscribeMail on Jmap server

Signed-off-by: dab246 <tdvu@linagora.com>
(cherry picked from commit 0f61a97aab513cda75c65e3c92ebcf8f6290b6ec)
This commit is contained in:
dab246
2023-11-16 12:48:38 +07:00
committed by Dat Vu
parent 4d8c7dc52e
commit a6ecaf321d
18 changed files with 169 additions and 36 deletions
@@ -231,6 +231,7 @@ extension AppColor on Color {
static const colorDropDownItemTitleComposer = Color(0xFF0A0A0A);
static const messageDialogColor = Color(0xFF8C9CAF);
static const messageDialogHighlightColor = Color(0xFF37383A);
static const labelColor = Color(0xFF71767C);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -14,6 +14,7 @@ class EmailRequest with EquatableMixin {
final IdentityId? identityId;
final EmailActionType emailActionType;
final String? storedSendingId;
final EmailId? previousEmailId;
EmailRequest({
required this.email,
@@ -23,6 +24,7 @@ class EmailRequest with EquatableMixin {
this.emailIdDestroyed,
this.emailIdAnsweredOrForwarded,
this.storedSendingId,
this.previousEmailId,
});
@override
@@ -34,6 +36,7 @@ class EmailRequest with EquatableMixin {
emailIdAnsweredOrForwarded,
emailActionType,
storedSendingId,
previousEmailId,
];
bool get isEmailAnswered => emailIdAnsweredOrForwarded != null &&
@@ -12,18 +12,18 @@ class SendEmailLoading extends UIState {}
class SendEmailSuccess extends UIActionState {
final String? storedSendingId;
final EmailRequest emailRequest;
SendEmailSuccess({
jmap.State? currentEmailState,
jmap.State? currentMailboxState,
this.storedSendingId,
required this.emailRequest,
}) : super(currentEmailState, currentMailboxState);
@override
List<Object?> get props => [
...super.props,
storedSendingId,
emailRequest,
];
}
@@ -52,7 +52,7 @@ class SendEmailInteractor {
SendEmailSuccess(
currentEmailState: currentEmailState,
currentMailboxState: currentMailboxState,
storedSendingId: emailRequest.storedSendingId
emailRequest: emailRequest
)
);
} else {
@@ -489,9 +489,7 @@ class ComposerController extends BaseController {
isInitialRecipient.value = true;
toAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (arguments.body?.isNotEmpty == true) {
_getEmailContentFromMailtoUri(arguments.body!);
}
_getEmailContentFromMailtoUri(arguments.body ?? '');
_updateStatusEmailSendButton();
break;
case EmailActionType.reply:
@@ -537,9 +535,7 @@ class ComposerController extends BaseController {
isInitialRecipient.value = true;
toAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (arguments.body?.isNotEmpty == true) {
_getEmailContentFromUnsubscribeMailtoLink(arguments.body!);
}
_getEmailContentFromUnsubscribeMailtoLink(arguments.body ?? '');
_updateStatusEmailSendButton();
break;
default:
@@ -969,7 +965,8 @@ class ComposerController extends BaseController {
? arguments.presentationEmail?.id
: null,
emailIdAnsweredOrForwarded: arguments.presentationEmail?.id,
emailActionType: arguments.emailActionType
emailActionType: arguments.emailActionType,
previousEmailId: arguments.previousEmailId,
);
final mailboxRequest = mailboxDashBoardController.outboxMailbox?.id == null
@@ -32,6 +32,7 @@ import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart';
import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
@@ -231,6 +232,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
mailboxDashBoardController.clearEmailUIAction();
}
});
ever(mailboxDashBoardController.viewState, (viewState) {
viewState.map((success) {
if (success is UnsubscribeEmailSuccess) {
emailUnsubscribe.value = null;
}
});
});
}
bool isListEmailContainSelectedEmail(PresentationEmail selectedEmail) {
@@ -441,7 +450,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_handleReadReceipt();
}
if (success.emailCurrent?.hasListUnsubscribe == true) {
if (currentEmail?.isSubscribed == false && success.emailCurrent?.hasListUnsubscribe == true) {
_handleUnsubscribe(success.emailCurrent!.listUnsubscribe);
} else {
emailUnsubscribe.value = null;
@@ -501,7 +510,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_handleReadReceipt();
}
if (success.emailCurrent?.hasListUnsubscribe == true) {
if (currentEmail?.isSubscribed == false && success.emailCurrent?.hasListUnsubscribe == true) {
_handleUnsubscribe(success.emailCurrent!.listUnsubscribe);
} else {
emailUnsubscribe.value = null;
@@ -951,11 +960,9 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void _markAsEmailStarSuccess(Success success) {
if (success is MarkAsStarEmailSuccess) {
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
mailboxDashBoardController.setSelectedEmail(selectedEmail?.updateKeywords(success.updatedEmail.keywords));
}
void _markAsEmailStarSuccess(MarkAsStarEmailSuccess success) {
final selectedEmail = mailboxDashBoardController.selectedEmail.value;
mailboxDashBoardController.setSelectedEmail(selectedEmail?.updateKeywords(success.updatedEmail.keywords));
mailboxDashBoardController.dispatchState(Right(success));
}
@@ -1406,9 +1413,17 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
AppLocalizations.of(context).unsubscribe,
onConfirmAction: () {
if (emailUnsubscribe.value?.httpLinks.isNotEmpty == true) {
_handleUnsubscribeMailByHttpsLink(context, emailUnsubscribe.value!.httpLinks);
_handleUnsubscribeMailByHttpsLink(
context: context,
emailId: presentationEmail.id!,
httpLinks: emailUnsubscribe.value!.httpLinks
);
} else if (emailUnsubscribe.value?.mailtoLinks.isNotEmpty == true) {
_handleUnsubscribeMailByMailtoLink(context, emailUnsubscribe.value!.mailtoLinks);
_handleUnsubscribeMailByMailtoLink(
context: context,
emailId: presentationEmail.id!,
mailtoLinks: emailUnsubscribe.value!.mailtoLinks
);
}
},
showAsBottomSheet: true,
@@ -1434,24 +1449,29 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
void _handleUnsubscribeMailByHttpsLink(BuildContext context, List<String> httpLinks) async {
void _handleUnsubscribeMailByHttpsLink({
required BuildContext context,
required EmailId emailId,
required List<String> httpLinks
}) {
log('SingleEmailController::_handleUnsubscribeMailByHttpsLink:httpLinks: $httpLinks');
final result = await AppUtils.launchLink(httpLinks.first);
if (result && context.mounted) {
_appToast.showToastSuccessMessage(
context,
AppLocalizations.of(context).unsubscribedFromThisMailingList);
}
mailboxDashBoardController.unsubscribeMail(emailId);
AppUtils.launchLink(httpLinks.first);
}
void _handleUnsubscribeMailByMailtoLink(BuildContext context, List<String> mailtoLinks) {
void _handleUnsubscribeMailByMailtoLink({
required BuildContext context,
required EmailId emailId,
required List<String> mailtoLinks
}) {
log('SingleEmailController::_handleUnsubscribeMailByMailtoLink:mailtoLinks: $mailtoLinks');
final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink(mailtoLinks.first);
mailboxDashBoardController.goToComposer(
ComposerArguments.fromUnsubscribeMailtoLink(
emailAddress: navigationRouter.emailAddress,
subject: navigationRouter.subject,
body: navigationRouter.body
body: navigationRouter.body,
previousEmailId: emailId,
)
);
}
@@ -33,6 +33,7 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/email_subject_
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_empty_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_loading_bar_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/mail_unsubscribed_banner.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
@@ -307,6 +308,10 @@ class EmailView extends GetWidget<SingleEmailController> {
openEmailAddressDetailAction: controller.openEmailAddressDialog,
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
)),
Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail,
emailUnsubscribe: controller.emailUnsubscribe.value
)),
Obx(() {
final attachments = controller.attachments.listAttachmentsDisplayedOutSide;
if (attachments.isNotEmpty) {
@@ -432,7 +437,7 @@ class EmailView extends GetWidget<SingleEmailController> {
EmailActionType.moveToSpam,
if (presentationEmail.from?.isNotEmpty == true)
EmailActionType.createRule,
if (controller.emailUnsubscribe.value != null)
if (!presentationEmail.isSubscribed && controller.emailUnsubscribe.value != null)
EmailActionType.unsubscribe
];
@@ -21,6 +21,7 @@ class ComposerArguments extends RouterArguments {
final String? body;
final MessageIdsHeaderValue? messageId;
final MessageIdsHeaderValue? references;
final EmailId? previousEmailId;
ComposerArguments({
this.emailActionType = EmailActionType.compose,
@@ -35,6 +36,7 @@ class ComposerArguments extends RouterArguments {
this.body,
this.messageId,
this.references,
this.previousEmailId,
});
factory ComposerArguments.fromSendingEmail(SendingEmail sendingEmail) =>
@@ -139,12 +141,18 @@ class ComposerArguments extends RouterArguments {
? SendingEmailActionType.edit
: SendingEmailActionType.create;
factory ComposerArguments.fromUnsubscribeMailtoLink({EmailAddress? emailAddress, String? subject, String? body}) =>
factory ComposerArguments.fromUnsubscribeMailtoLink({
EmailAddress? emailAddress,
String? subject,
String? body,
EmailId? previousEmailId
}) =>
ComposerArguments(
emailActionType: EmailActionType.composeFromUnsubscribeMailtoLink,
emailAddress: emailAddress,
subject: subject,
body: body,
previousEmailId: previousEmailId,
);
@@ -63,7 +63,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
openEmailAddressDetailAction: openEmailAddressDetailAction,
)
)),
if (emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
if (!emailSelected.isSubscribed && emailUnsubscribe != null && !responsiveUtils.isPortraitMobile(context))
TMailButtonWidget.fromText(
text: AppLocalizations.of(context).unsubscribe,
textStyle: const TextStyle(
@@ -0,0 +1,36 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:flutter/material.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
class MailUnsubscribedBanner extends StatelessWidget {
final PresentationEmail? presentationEmail;
final EmailUnsubscribe? emailUnsubscribe;
const MailUnsubscribedBanner({
super.key,
required this.presentationEmail,
this.emailUnsubscribe
});
@override
Widget build(BuildContext context) {
if (presentationEmail?.isSubscribed == true && emailUnsubscribe == null) {
return Padding(
padding: const EdgeInsetsDirectional.only(bottom: 16, start: 20, end: 20),
child: Text(
AppLocalizations.of(context).mailUnsubscribedMessage(presentationEmail?.firstEmailAddressInFrom ?? ''),
style: const TextStyle(
fontWeight: FontWeight.normal,
fontSize: 15,
color: AppColor.labelColor
),
),
);
} else {
return const SizedBox.shrink();
}
}
}
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_ema
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/unsubscribe_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/email_supervisor_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
@@ -165,6 +166,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.find<SaveEmailAsDraftsInteractor>(),
Get.find<UpdateEmailDraftsInteractor>(),
Get.find<DeleteSendingEmailInteractor>(),
Get.find<UnsubscribeEmailInteractor>(),
));
Get.put(AdvancedFilterController());
}
@@ -306,6 +308,7 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.find<EmailRepository>(),
Get.find<MailboxRepository>()
));
Get.lazyPut(() => UnsubscribeEmailInteractor(Get.find<EmailRepository>()));
}
@override
@@ -49,11 +49,13 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_sending_email_s
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/store_sending_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_email_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/delete_multiple_emails_permanently_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/unsubscribe_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.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';
@@ -174,6 +176,7 @@ class MailboxDashBoardController extends ReloadableController {
final SaveEmailAsDraftsInteractor _saveEmailAsDraftsInteractor;
final UpdateEmailDraftsInteractor _updateEmailDraftsInteractor;
final DeleteSendingEmailInteractor _deleteSendingEmailInteractor;
final UnsubscribeEmailInteractor _unsubscribeEmailInteractor;
GetAllVacationInteractor? _getAllVacationInteractor;
UpdateVacationInteractor? _updateVacationInteractor;
@@ -251,6 +254,7 @@ class MailboxDashBoardController extends ReloadableController {
this._saveEmailAsDraftsInteractor,
this._updateEmailDraftsInteractor,
this._deleteSendingEmailInteractor,
this._unsubscribeEmailInteractor,
);
@override
@@ -343,6 +347,8 @@ class MailboxDashBoardController extends ReloadableController {
_markAsReadEmailSuccess(success);
} else if (success is DeleteSendingEmailSuccess) {
getAllSendingEmails();
} else if (success is UnsubscribeEmailSuccess) {
_handleUnsubscribeMailSuccess(success.newEmail);
}
}
@@ -2123,14 +2129,14 @@ class MailboxDashBoardController extends ReloadableController {
void _handleSendEmailSuccess(SendEmailSuccess success) {
if (PlatformInfo.isIOS &&
success.storedSendingId != null &&
success.emailRequest.storedSendingId != null &&
accountId.value != null &&
sessionCurrent != null
) {
consumeState(_deleteSendingEmailInteractor.execute(
accountId.value!,
sessionCurrent!.username,
success.storedSendingId!
success.emailRequest.storedSendingId!
));
}
if (currentOverlayContext != null && currentContext != null) {
@@ -2140,6 +2146,11 @@ class MailboxDashBoardController extends ReloadableController {
leadingSVGIcon: imagePaths.icSendSuccessToast
);
}
if (success.emailRequest.emailActionType == EmailActionType.composeFromUnsubscribeMailtoLink &&
success.emailRequest.previousEmailId != null) {
unsubscribeMail(success.emailRequest.previousEmailId!);
}
}
void _updateSendingStateWhenSendEmailFailureOnIOS(SendEmailFailure failure) {
@@ -2192,6 +2203,27 @@ class MailboxDashBoardController extends ReloadableController {
dispatchAction(SearchEmailByFromFieldsAction(emailAddress));
}
void unsubscribeMail(EmailId emailId) {
if (accountId.value != null && sessionCurrent != null) {
consumeState(
_unsubscribeEmailInteractor.execute(
sessionCurrent!,
accountId.value!,
emailId
)
);
}
}
void _handleUnsubscribeMailSuccess(Email email) {
if (currentContext != null && currentOverlayContext != null) {
appToast.showToastSuccessMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).unsubscribedFromThisMailingList);
}
setSelectedEmail(email.toPresentationEmail());
}
@override
void onClose() {
_emailReceiveManager.closeEmailReceiveManagerStream();
@@ -37,6 +37,7 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
@@ -209,7 +210,8 @@ class SearchEmailController extends BaseController
success is EmptyTrashFolderSuccess ||
success is EmptySpamFolderSuccess ||
success is DeleteMultipleEmailsPermanentlyAllSuccess ||
success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure
success is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure ||
success is UnsubscribeEmailSuccess
) {
_refreshEmailChanges();
}
@@ -28,6 +28,7 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_email_state.dart';
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart';
@@ -348,6 +349,8 @@ class ThreadController extends BaseController with EmailActionController {
_refreshEmailChanges(currentEmailState: success.currentEmailState);
} else if (success is EmptyTrashFolderSuccess || success is EmptySpamFolderSuccess) {
refreshAllEmail();
} else if (success is UnsubscribeEmailSuccess) {
_refreshEmailChanges(currentEmailState: success.currentEmailState);
}
});
});
+11 -1
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2023-11-16T00:29:04.650547",
"@@last_modified": "2023-11-16T12:39:33.304952",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -3517,5 +3517,15 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"mailUnsubscribedMessage": "You unsubscribe from {senderName}",
"@mailUnsubscribedMessage": {
"type": "text",
"placeholders_order": [
"senderName"
],
"placeholders": {
"senderName": {}
}
}
}
@@ -3652,4 +3652,12 @@ class AppLocalizations {
name: 'unsubscribedFromThisMailingList',
);
}
String mailUnsubscribedMessage(String senderName) {
return Intl.message(
'You unsubscribe from $senderName',
name: 'mailUnsubscribedMessage',
args: [senderName]
);
}
}
+3
View File
@@ -13,6 +13,7 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/email/email_content.dart';
import 'package:model/extensions/email_address_extension.dart';
import 'package:model/extensions/keyword_identifier_extension.dart';
import 'package:model/extensions/media_type_nullable_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart';
@@ -110,6 +111,8 @@ class PresentationEmail with EquatableMixin {
bool get isForwarded => keywords?.containsKey(KeyWordIdentifier.emailForwarded) == true;
bool get isSubscribed => keywords?.containsKey(KeyWordIdentifierExtension.unsubscribeMail) == true;
bool get isAnsweredAndForwarded => isAnswered && isForwarded;
bool get withAttachments => hasAttachment == true;
@@ -25,6 +25,8 @@ extension EmailExtension on Email {
bool get isDraft => keywords?.containsKey(KeyWordIdentifier.emailDraft) == true;
bool get isUnsubscribed => keywords?.containsKey(KeyWordIdentifierExtension.unsubscribeMail) == true;
bool get withAttachments => hasAttachment == true;
String get listUnsubscribe {