TF-4236 Implement remove a label from an email when unselect in context menu Label as

This commit is contained in:
dab246
2026-01-09 09:34:04 +07:00
committed by Dat H. Pham
parent 36e573db23
commit 3ef66bbbda
11 changed files with 376 additions and 29 deletions
@@ -5,6 +5,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i
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_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/mark_as_star_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/email_interactor_bindings.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/email_interactor_bindings.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
@@ -25,6 +26,7 @@ class EmailBindings extends Bindings {
Get.find<GetAllIdentitiesInteractor>(), Get.find<GetAllIdentitiesInteractor>(),
Get.find<StoreOpenedEmailInteractor>(), Get.find<StoreOpenedEmailInteractor>(),
Get.find<AddALabelToAnEmailInteractor>(), Get.find<AddALabelToAnEmailInteractor>(),
Get.find<RemoveALabelFromAnEmailInteractor>(),
Get.find<PrintEmailInteractor>(), Get.find<PrintEmailInteractor>(),
currentEmailId: currentEmailId, currentEmailId: currentEmailId,
), tag: tag); ), tag: tag);
@@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_
import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_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/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.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/state_datasource.dart';
@@ -121,6 +122,9 @@ class EmailInteractorBindings extends InteractorsBindings {
Get.lazyPut<AddALabelToAnEmailInteractor>( Get.lazyPut<AddALabelToAnEmailInteractor>(
() => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()), () => AddALabelToAnEmailInteractor(Get.find<EmailRepository>()),
); );
Get.lazyPut<RemoveALabelFromAnEmailInteractor>(
() => RemoveALabelFromAnEmailInteractor(Get.find<EmailRepository>()),
);
} }
@override @override
@@ -47,6 +47,7 @@ import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_sta
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_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/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/remove_a_label_from_an_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/send_receipt_to_sender_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/send_receipt_to_sender_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/unsubscribe_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/add_a_label_to_an_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/add_a_label_to_an_email_interactor.dart';
@@ -60,6 +61,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_
import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/send_receipt_to_sender_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/send_receipt_to_sender_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_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'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
@@ -119,6 +121,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
final StoreOpenedEmailInteractor _storeOpenedEmailInteractor; final StoreOpenedEmailInteractor _storeOpenedEmailInteractor;
final PrintEmailInteractor _printEmailInteractor; final PrintEmailInteractor _printEmailInteractor;
final AddALabelToAnEmailInteractor addALabelToAnEmailInteractor; final AddALabelToAnEmailInteractor addALabelToAnEmailInteractor;
final RemoveALabelFromAnEmailInteractor removeALabelFromAnEmailInteractor;
final EmailId? _currentEmailId; final EmailId? _currentEmailId;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
@@ -189,6 +192,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
this._getAllIdentitiesInteractor, this._getAllIdentitiesInteractor,
this._storeOpenedEmailInteractor, this._storeOpenedEmailInteractor,
this.addALabelToAnEmailInteractor, this.addALabelToAnEmailInteractor,
this.removeALabelFromAnEmailInteractor,
this._printEmailInteractor, { this._printEmailInteractor, {
EmailId? currentEmailId, EmailId? currentEmailId,
}) : _currentEmailId = currentEmailId; }) : _currentEmailId = currentEmailId;
@@ -247,6 +251,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
calendarEventSuccess(success); calendarEventSuccess(success);
} else if (success is AddALabelToAnEmailSuccess) { } else if (success is AddALabelToAnEmailSuccess) {
handleAddLabelToEmailSuccess(success); handleAddLabelToEmailSuccess(success);
} else if (success is RemoveALabelFromAnEmailSuccess) {
handleRemoveLabelFromEmailSuccess(success);
} else { } else {
super.handleSuccessViewState(success); super.handleSuccessViewState(success);
} }
@@ -266,6 +272,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_calendarEventFailure(failure); _calendarEventFailure(failure);
} else if (failure is AddALabelToAnEmailFailure) { } else if (failure is AddALabelToAnEmailFailure) {
handleAddLabelToEmailFailure(failure); handleAddLabelToEmailFailure(failure);
} else if (failure is RemoveALabelFromAnEmailFailure) {
handleRemoveLabelFromEmailFailure(failure);
} else { } else {
super.handleFailureViewState(failure); super.handleFailureViewState(failure);
} }
@@ -1,5 +1,4 @@
import 'package:core/utils/platform_info.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/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
@@ -8,6 +7,7 @@ import 'package:labels/extensions/label_extension.dart';
import 'package:labels/model/label.dart'; import 'package:labels/model/label.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/email/domain/state/add_a_label_to_an_email_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/add_a_label_to_an_email_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/remove_a_label_from_an_email_state.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/email_loaded_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/email_loaded_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/presentation_email_extension.dart';
@@ -27,16 +27,23 @@ extension HandleLabelForEmailExtension on SingleEmailController {
} }
void toggleLabelToEmail(EmailId emailId, Label label, bool isSelected) { void toggleLabelToEmail(EmailId emailId, Label label, bool isSelected) {
if (isSelected) { final accountId = mailboxDashBoardController.accountId.value;
final accountId = mailboxDashBoardController.accountId.value; final session = mailboxDashBoardController.sessionCurrent;
final session = mailboxDashBoardController.sessionCurrent;
if (isSelected) {
_addALabelToAnEmail( _addALabelToAnEmail(
session: session, session: session,
accountId: accountId, accountId: accountId,
emailId: emailId, emailId: emailId,
label: label, label: label,
); );
} else {
_removeALabelFromAnEmail(
session: session,
accountId: accountId,
emailId: emailId,
label: label,
);
} }
} }
@@ -49,24 +56,22 @@ extension HandleLabelForEmailExtension on SingleEmailController {
final labelDisplay = label.safeDisplayName; final labelDisplay = label.safeDisplayName;
if (session == null) { if (session == null) {
consumeState( emitFailure(
Stream.value( controller: this,
Left(AddALabelToAnEmailFailure( failure: AddALabelToAnEmailFailure(
exception: NotFoundSessionException(), exception: NotFoundSessionException(),
labelDisplay: labelDisplay, labelDisplay: labelDisplay,
)),
), ),
); );
return; return;
} }
if (accountId == null) { if (accountId == null) {
consumeState( emitFailure(
Stream.value( controller: this,
Left(AddALabelToAnEmailFailure( failure: AddALabelToAnEmailFailure(
exception: NotFoundAccountIdException(), exception: NotFoundAccountIdException(),
labelDisplay: labelDisplay, labelDisplay: labelDisplay,
)),
), ),
); );
return; return;
@@ -74,13 +79,12 @@ extension HandleLabelForEmailExtension on SingleEmailController {
final labelKeyword = label.keyword; final labelKeyword = label.keyword;
if (labelKeyword == null) { if (labelKeyword == null) {
consumeState( emitFailure(
Stream.value(Left( controller: this,
AddALabelToAnEmailFailure( failure: AddALabelToAnEmailFailure(
exception: LabelKeywordIsNull(), exception: LabelKeywordIsNull(),
labelDisplay: labelDisplay, labelDisplay: labelDisplay,
), ),
)),
); );
return; return;
} }
@@ -100,6 +104,7 @@ extension HandleLabelForEmailExtension on SingleEmailController {
_autoSyncLabelToSelectedEmailOnMemory( _autoSyncLabelToSelectedEmailOnMemory(
emailId: success.emailId, emailId: success.emailId,
labelKeyword: success.labelKeyword, labelKeyword: success.labelKeyword,
remove: false,
); );
} }
@@ -110,11 +115,13 @@ extension HandleLabelForEmailExtension on SingleEmailController {
void _autoSyncLabelToSelectedEmailOnMemory({ void _autoSyncLabelToSelectedEmailOnMemory({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool remove,
}) { }) {
_updateLabelInEmailOnMemory( _updateLabelInEmailOnMemory(
emailId: emailId, emailId: emailId,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
isMobileThreadDisabled: PlatformInfo.isMobile && !isThreadDetailEnabled, isMobileThreadDisabled: PlatformInfo.isMobile && !isThreadDetailEnabled,
remove: remove,
); );
} }
@@ -122,27 +129,32 @@ extension HandleLabelForEmailExtension on SingleEmailController {
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool isMobileThreadDisabled, required bool isMobileThreadDisabled,
required bool remove,
}) { }) {
_updateLabelOnSelectedEmailIfNeeded( _updateLabelOnSelectedEmailIfNeeded(
emailId: emailId, emailId: emailId,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
isMobileThreadDisabled: isMobileThreadDisabled, isMobileThreadDisabled: isMobileThreadDisabled,
remove: remove,
); );
_updateLabelOnThreadIfNeeded( _updateLabelOnThreadIfNeeded(
emailId: emailId, emailId: emailId,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
isMobileThreadDisabled: isMobileThreadDisabled, isMobileThreadDisabled: isMobileThreadDisabled,
remove: remove,
); );
_updateLabelOnCurrentEmailLoaded( _updateLabelOnCurrentEmailLoaded(
emailId: emailId, emailId: emailId,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
remove: remove,
); );
_notifyLabelUpdated( _notifyLabelUpdated(
emailId: emailId, emailId: emailId,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
remove: remove,
); );
} }
@@ -150,19 +162,21 @@ extension HandleLabelForEmailExtension on SingleEmailController {
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool isMobileThreadDisabled, required bool isMobileThreadDisabled,
required bool remove,
}) { }) {
if (!isMobileThreadDisabled) return; if (!isMobileThreadDisabled) return;
final selectedEmail = mailboxDashBoardController.selectedEmail.value; final selectedEmail = mailboxDashBoardController.selectedEmail.value;
if (selectedEmail?.id != emailId) return; if (selectedEmail?.id != emailId) return;
selectedEmail?.keywords?.addKeyword(labelKeyword); selectedEmail?.keywords?.toggleKeyword(labelKeyword, remove);
} }
void _updateLabelOnThreadIfNeeded({ void _updateLabelOnThreadIfNeeded({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool isMobileThreadDisabled, required bool isMobileThreadDisabled,
required bool remove,
}) { }) {
if (isMobileThreadDisabled) return; if (isMobileThreadDisabled) return;
@@ -173,20 +187,21 @@ extension HandleLabelForEmailExtension on SingleEmailController {
controller.emailIdsPresentation.toggleEmailKeywordById( controller.emailIdsPresentation.toggleEmailKeywordById(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false, remove: remove,
); );
controller.emailsInThreadDetailInfo.value = controller.emailsInThreadDetailInfo.value =
controller.emailsInThreadDetailInfo.toggleEmailKeywordById( controller.emailsInThreadDetailInfo.toggleEmailKeywordById(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false, remove: remove,
); );
} }
void _updateLabelOnCurrentEmailLoaded({ void _updateLabelOnCurrentEmailLoaded({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool remove,
}) { }) {
final emailLoaded = currentEmailLoaded.value; final emailLoaded = currentEmailLoaded.value;
if (emailLoaded == null) return; if (emailLoaded == null) return;
@@ -195,17 +210,18 @@ extension HandleLabelForEmailExtension on SingleEmailController {
currentEmailLoaded.value = emailLoaded.toggleEmailKeyword( currentEmailLoaded.value = emailLoaded.toggleEmailKeyword(
emailId: emailId, emailId: emailId,
keyword: labelKeyword, keyword: labelKeyword,
remove: false, remove: remove,
); );
} }
void _notifyLabelUpdated({ void _notifyLabelUpdated({
required EmailId emailId, required EmailId emailId,
required KeyWordIdentifier labelKeyword, required KeyWordIdentifier labelKeyword,
required bool remove,
}) { }) {
mailboxDashBoardController.updateEmailFlagByEmailIds( mailboxDashBoardController.updateEmailFlagByEmailIds(
[emailId], [emailId],
isLabelAdded: true, isLabelAdded: !remove,
labelKeyword: labelKeyword, labelKeyword: labelKeyword,
); );
@@ -235,4 +251,73 @@ extension HandleLabelForEmailExtension on SingleEmailController {
dialogLabel: 'add-label-to-email-modal', dialogLabel: 'add-label-to-email-modal',
); );
} }
void _removeALabelFromAnEmail({
required Session? session,
required AccountId? accountId,
required Label label,
required EmailId emailId,
}) {
final labelDisplay = label.safeDisplayName;
if (session == null) {
emitFailure(
controller: this,
failure: RemoveALabelFromAnEmailFailure(
exception: NotFoundSessionException(),
labelDisplay: labelDisplay,
),
);
return;
}
if (accountId == null) {
emitFailure(
controller: this,
failure: RemoveALabelFromAnEmailFailure(
exception: NotFoundAccountIdException(),
labelDisplay: labelDisplay,
),
);
return;
}
final labelKeyword = label.keyword;
if (labelKeyword == null) {
emitFailure(
controller: this,
failure: RemoveALabelFromAnEmailFailure(
exception: LabelKeywordIsNull(),
labelDisplay: labelDisplay,
),
);
return;
}
consumeState(removeALabelFromAnEmailInteractor.execute(
session,
accountId,
emailId,
labelKeyword,
label.safeDisplayName,
));
}
void handleRemoveLabelFromEmailSuccess(
RemoveALabelFromAnEmailSuccess success,
) {
toastManager.showMessageSuccess(success);
_autoSyncLabelToSelectedEmailOnMemory(
emailId: success.emailId,
labelKeyword: success.labelKeyword,
remove: true,
);
}
void handleRemoveLabelFromEmailFailure(
RemoveALabelFromAnEmailFailure failure,
) {
toastManager.showMessageFailure(failure);
}
} }
@@ -27,4 +27,16 @@ extension MapKeywordsExtension on Map<KeyWordIdentifier, bool>? {
void addKeyword(KeyWordIdentifier keyword) { void addKeyword(KeyWordIdentifier keyword) {
this?[keyword] = true; this?[keyword] = true;
} }
void removeKeyword(KeyWordIdentifier keyword) {
this?.remove(keyword);
}
void toggleKeyword(KeyWordIdentifier keyword, bool remove) {
if (remove) {
this?.remove(keyword);
} else {
this?[keyword] = true;
}
}
} }
+20
View File
@@ -5381,5 +5381,25 @@
"type": "text", "type": "text",
"placeholders_order": [], "placeholders_order": [],
"placeholders": {} "placeholders": {}
},
"removeLabelFromEmailSuccessfullyMessage": "Email removed from the \"{labelName}\" label",
"@removeLabelFromEmailSuccessfullyMessage": {
"type": "text",
"placeholders_order": [
"labelName"
],
"placeholders": {
"labelName": {}
}
},
"removeLabelFromEmailFailureMessage": "Cannot remove email from the \"{labelName}\" label",
"@removeLabelFromEmailFailureMessage": {
"type": "text",
"placeholders_order": [
"labelName"
],
"placeholders": {
"labelName": {}
}
} }
} }
@@ -5709,4 +5709,19 @@ class AppLocalizations {
name: 'editLabelFailure', name: 'editLabelFailure',
); );
} }
String removeLabelFromEmailSuccessfullyMessage(String labelName) {
return Intl.message(
'Email removed from the "$labelName" label',
name: 'removeLabelFromEmailSuccessfullyMessage',
args: [labelName],
);
}
String removeLabelFromEmailFailureMessage(String labelName) {
return Intl.message(
'Cannot remove email from the "$labelName" label',
name: 'removeLabelFromEmailFailureMessage',
args: [labelName],
);
}
} }
+10
View File
@@ -28,6 +28,7 @@ import 'package:tmail_ui_user/features/email/domain/state/calendar_event_reply_s
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart'; import 'package:tmail_ui_user/features/download/domain/state/parse_email_by_blob_id_state.dart';
import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart'; import 'package:tmail_ui_user/features/download/domain/state/preview_email_from_eml_file_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/remove_a_label_from_an_email_state.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart'; import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart';
import 'package:tmail_ui_user/features/labels/domain/state/create_new_label_state.dart'; import 'package:tmail_ui_user/features/labels/domain/state/create_new_label_state.dart';
@@ -212,6 +213,11 @@ class ToastManager {
appLocalizations.addLabelToThreadFailureMessage(failure.labelDisplay); appLocalizations.addLabelToThreadFailureMessage(failure.labelDisplay);
} else if (failure is EditLabelFailure) { } else if (failure is EditLabelFailure) {
message = message ?? appLocalizations.editLabelFailure; message = message ?? appLocalizations.editLabelFailure;
} else if (failure is RemoveALabelFromAnEmailFailure) {
message = message ??
appLocalizations.removeLabelFromEmailFailureMessage(
failure.labelDisplay,
);
} }
log('ToastManager::showMessageFailure: Message: $message'); log('ToastManager::showMessageFailure: Message: $message');
if (message?.trim().isNotEmpty == true) { if (message?.trim().isNotEmpty == true) {
@@ -291,6 +297,10 @@ class ToastManager {
message = appLocalizations.addLabelToThreadSuccessfullyMessage( message = appLocalizations.addLabelToThreadSuccessfullyMessage(
success.labelDisplay, success.labelDisplay,
); );
} else if (success is RemoveALabelFromAnEmailSuccess) {
message = appLocalizations.removeLabelFromEmailSuccessfullyMessage(
success.labelDisplay,
);
} }
log('ToastManager::showMessageSuccess: Message: $message'); log('ToastManager::showMessageSuccess: Message: $message');
if (message?.trim().isNotEmpty == true) { if (message?.trim().isNotEmpty == true) {
@@ -28,5 +28,6 @@ extension KeyWordIdentifierExtension on KeyWordIdentifier {
PatchObject generateUnsubscribeActionPath() => _boolPatch(true); PatchObject generateUnsubscribeActionPath() => _boolPatch(true);
PatchObject generateLabelActionPath() => _boolPatch(true); PatchObject generateLabelActionPath({bool remove = false}) =>
_boolPatch(remove ? null : true);
} }
@@ -36,6 +36,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_
import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/maybe_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/remove_a_label_from_an_email_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_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'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
@@ -75,6 +76,7 @@ const fallbackGenerators = {
MockSpec<GetAllIdentitiesInteractor>(), MockSpec<GetAllIdentitiesInteractor>(),
MockSpec<StoreOpenedEmailInteractor>(), MockSpec<StoreOpenedEmailInteractor>(),
MockSpec<AddALabelToAnEmailInteractor>(), MockSpec<AddALabelToAnEmailInteractor>(),
MockSpec<RemoveALabelFromAnEmailInteractor>(),
MockSpec<MailboxDashBoardController>(fallbackGenerators: fallbackGenerators), MockSpec<MailboxDashBoardController>(fallbackGenerators: fallbackGenerators),
MockSpec<DownloadController>(fallbackGenerators: fallbackGenerators), MockSpec<DownloadController>(fallbackGenerators: fallbackGenerators),
MockSpec<DownloadManager>(fallbackGenerators: fallbackGenerators), MockSpec<DownloadManager>(fallbackGenerators: fallbackGenerators),
@@ -109,6 +111,7 @@ void main() {
final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor(); final getAllIdentitiesInteractor = MockGetAllIdentitiesInteractor();
final storeOpenedEmailInteractor = MockStoreOpenedEmailInteractor(); final storeOpenedEmailInteractor = MockStoreOpenedEmailInteractor();
final addALabelToAnEmailInteractor = MockAddALabelToAnEmailInteractor(); final addALabelToAnEmailInteractor = MockAddALabelToAnEmailInteractor();
final removeALabelFromAnEmailInteractor = MockRemoveALabelFromAnEmailInteractor();
final mailboxDashboardController = MockMailboxDashBoardController(); final mailboxDashboardController = MockMailboxDashBoardController();
final downloadController = MockDownloadController(); final downloadController = MockDownloadController();
final downloadManager = MockDownloadManager(); final downloadManager = MockDownloadManager();
@@ -175,6 +178,7 @@ void main() {
getAllIdentitiesInteractor, getAllIdentitiesInteractor,
storeOpenedEmailInteractor, storeOpenedEmailInteractor,
addALabelToAnEmailInteractor, addALabelToAnEmailInteractor,
removeALabelFromAnEmailInteractor,
printEmailInteractor, printEmailInteractor,
); );
}); });
@@ -171,4 +171,190 @@ void main() {
expect(map[keyword], true); expect(map[keyword], true);
}); });
}); });
group('MapKeywordsExtension - removeKeyword()', () {
test('removes existing keyword from map', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: true,
KeyWordIdentifier('\$flagged'): true,
};
map.removeKeyword(keyword);
expect(map.length, 1);
expect(map.containsKey(keyword), false);
});
test('removes keyword even if its value is false', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: false,
};
map.removeKeyword(keyword);
expect(map.isEmpty, true);
});
test('does nothing when keyword does not exist', () {
final map = {
KeyWordIdentifier('\$flagged'): true,
};
final keyword = KeyWordIdentifier('\$seen');
map.removeKeyword(keyword);
expect(map.length, 1);
expect(map.values.first, true);
});
test('does not throw when map is empty', () {
final map = <KeyWordIdentifier, bool>{};
final keyword = KeyWordIdentifier('\$seen');
expect(() => map.removeKeyword(keyword), returnsNormally);
expect(map.isEmpty, true);
});
test('does not throw when map is null', () {
Map<KeyWordIdentifier, bool>? map;
final keyword = KeyWordIdentifier('\$seen');
expect(() => map.removeKeyword(keyword), returnsNormally);
});
test('calling removeKeyword on null map has no side effects', () {
Map<KeyWordIdentifier, bool>? map;
final keyword = KeyWordIdentifier('\$seen');
map.removeKeyword(keyword);
expect(map, isNull);
});
test('mutates the same map instance', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: true,
};
final originalIdentity = identityHashCode(map);
map.removeKeyword(keyword);
expect(identityHashCode(map), originalIdentity);
expect(map.isEmpty, true);
});
});
group('MapKeywordsExtension - toggleKeyword()', () {
test('adds keyword when remove is false on empty map', () {
final keyword = KeyWordIdentifier('\$seen');
final map = <KeyWordIdentifier, bool>{};
map.toggleKeyword(keyword, false);
expect(map.length, 1);
expect(map[keyword], true);
});
test('adds keyword when remove is false on non-empty map', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
KeyWordIdentifier('\$flagged'): true,
};
map.toggleKeyword(keyword, false);
expect(map.length, 2);
expect(map[keyword], true);
});
test('overwrites existing keyword value to true when remove is false', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: false,
};
map.toggleKeyword(keyword, false);
expect(map.length, 1);
expect(map[keyword], true);
});
test('removes keyword when remove is true', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: true,
KeyWordIdentifier('\$flagged'): true,
};
map.toggleKeyword(keyword, true);
expect(map.length, 1);
expect(map.containsKey(keyword), false);
});
test('does nothing when remove is true and keyword does not exist', () {
final map = {
KeyWordIdentifier('\$flagged'): true,
};
final keyword = KeyWordIdentifier('\$seen');
map.toggleKeyword(keyword, true);
expect(map.length, 1);
expect(map.values.first, true);
});
test('does not throw when map is empty and remove is true', () {
final map = <KeyWordIdentifier, bool>{};
final keyword = KeyWordIdentifier('\$seen');
expect(() => map.toggleKeyword(keyword, true), returnsNormally);
expect(map.isEmpty, true);
});
test('adds keyword when map is empty and remove is false', () {
final map = <KeyWordIdentifier, bool>{};
final keyword = KeyWordIdentifier('\$seen');
expect(() => map.toggleKeyword(keyword, false), returnsNormally);
expect(map.length, 1);
expect(map[keyword], true);
});
test('does not throw when map is null', () {
Map<KeyWordIdentifier, bool>? map;
final keyword = KeyWordIdentifier('\$seen');
expect(() => map.toggleKeyword(keyword, true), returnsNormally);
expect(() => map.toggleKeyword(keyword, false), returnsNormally);
});
test('calling toggleKeyword on null map has no side effects', () {
Map<KeyWordIdentifier, bool>? map;
final keyword = KeyWordIdentifier('\$seen');
map.toggleKeyword(keyword, false);
map.toggleKeyword(keyword, true);
expect(map, isNull);
});
test('mutates the same map instance', () {
final keyword = KeyWordIdentifier('\$seen');
final map = {
keyword: true,
};
final originalIdentity = identityHashCode(map);
map.toggleKeyword(keyword, true);
expect(identityHashCode(map), originalIdentity);
expect(map.isEmpty, true);
});
});
} }