TF-3189 new option to copy a folder's subaddress
This commit is contained in:
committed by
Dat H. Pham
parent
89d80fe142
commit
f1ec1d7324
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M17 2C18.3063 2 19.4175 2.83485 19.8293 4.00009L10.1278 4C8.34473 4 7.69816 4.18565 7.04631 4.53427C6.39446 4.88288 5.88288 5.39446 5.53427 6.04631C5.18565 6.69816 5 7.34473 5 9.12777L5.00009 19.8293C3.83485 19.4175 3 18.3063 3 17V8C3 4.68629 5.68629 2 9 2H17ZM21 6C22.6569 6 24 7.34315 24 9V22C24 23.6569 22.6569 25 21 25H10C8.34315 25 7 23.6569 7 22V9C7 7.34315 8.34315 6 10 6H21ZM21 8H10C9.44772 8 9 8.44772 9 9V22C9 22.5523 9.44772 23 10 23H21C21.5523 23 22 22.5523 22 22V9C22 8.44772 21.5523 8 21 8Z" fill="#007AFF"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 635 B |
@@ -223,6 +223,7 @@ class ImagePaths {
|
|||||||
String get icLogoTwakeWelcome => _getImagePath('ic_logo_twake_welcome.svg');
|
String get icLogoTwakeWelcome => _getImagePath('ic_logo_twake_welcome.svg');
|
||||||
String get icHelp => _getImagePath('ic_help.svg');
|
String get icHelp => _getImagePath('ic_help.svg');
|
||||||
String get icDownloadAll => _getImagePath('ic_download_all.svg');
|
String get icDownloadAll => _getImagePath('ic_download_all.svg');
|
||||||
|
String get icCopy => _getImagePath('ic_copy.svg');
|
||||||
String get icSubaddressingAllow => _getImagePath('ic_subaddressing_allow.svg');
|
String get icSubaddressingAllow => _getImagePath('ic_subaddressing_allow.svg');
|
||||||
String get icSubaddressingDisallow => _getImagePath('ic_subaddressing_disallow.svg');
|
String get icSubaddressingDisallow => _getImagePath('ic_subaddressing_disallow.svg');
|
||||||
|
|
||||||
|
|||||||
@@ -216,14 +216,18 @@ abstract class BaseMailboxController extends BaseController {
|
|||||||
return teamMailboxesTree.value.findNode((node) => node.item.id == mailboxId);
|
return teamMailboxesTree.value.findNode((node) => node.item.id == mailboxId);
|
||||||
}
|
}
|
||||||
|
|
||||||
String? findNodePath(MailboxId mailboxId) {
|
String? findNodePathWithSeparator(MailboxId mailboxId, String pathSeparator) {
|
||||||
var mailboxNodePath = defaultMailboxTree.value.getNodePath(mailboxId)
|
var mailboxNodePath = defaultMailboxTree.value.getNodePath(mailboxId, pathSeparator)
|
||||||
?? personalMailboxTree.value.getNodePath(mailboxId)
|
?? personalMailboxTree.value.getNodePath(mailboxId, pathSeparator)
|
||||||
?? teamMailboxesTree.value.getNodePath(mailboxId);
|
?? teamMailboxesTree.value.getNodePath(mailboxId, pathSeparator);
|
||||||
log('BaseMailboxController::findNodePath():mailboxNodePath: $mailboxNodePath');
|
log('BaseMailboxController::findNodePath():mailboxNodePath: $mailboxNodePath');
|
||||||
return mailboxNodePath;
|
return mailboxNodePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String? findNodePath(MailboxId mailboxId) {
|
||||||
|
return findNodePathWithSeparator(mailboxId, '/');
|
||||||
|
}
|
||||||
|
|
||||||
MailboxNode? findMailboxNodeByRole(Role role) {
|
MailboxNode? findMailboxNodeByRole(Role role) {
|
||||||
final mailboxNode = defaultMailboxTree.value.findNode((node) => node.item.role == role);
|
final mailboxNode = defaultMailboxTree.value.findNode((node) => node.item.role == role);
|
||||||
return mailboxNode;
|
return mailboxNode;
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
class EmptyFolderNameException implements Exception {
|
||||||
|
final String folderName;
|
||||||
|
|
||||||
|
EmptyFolderNameException(this.folderName);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'EmptyFolderNameException: Folder name should not be empty: $folderName';
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
|
||||||
|
class InvalidMailFormatException implements Exception {
|
||||||
|
final String mail;
|
||||||
|
|
||||||
|
InvalidMailFormatException(this.mail);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'InvalidMailFormatException: $mail';
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import 'package:core/core.dart';
|
|||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:jmap_dart_client/jmap/account_id.dart';
|
import 'package:jmap_dart_client/jmap/account_id.dart';
|
||||||
@@ -30,6 +31,8 @@ import 'package:tmail_ui_user/features/email/presentation/model/composer_argumen
|
|||||||
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/extensions/session_extensions.dart';
|
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/constants/mailbox_constants.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/constants/mailbox_constants.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/empty_folder_name_exception.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/invalid_mail_format_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/set_mailbox_name_exception.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/set_mailbox_name_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/null_session_or_accountid_exception.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/null_session_or_accountid_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
|
||||||
@@ -1248,6 +1251,14 @@ class MailboxController extends BaseMailboxController
|
|||||||
case MailboxActions.openInNewTab:
|
case MailboxActions.openInNewTab:
|
||||||
openMailboxInNewTabAction(mailbox);
|
openMailboxInNewTabAction(mailbox);
|
||||||
break;
|
break;
|
||||||
|
case MailboxActions.copySubaddress:
|
||||||
|
try{
|
||||||
|
final subaddress = getSubaddress(mailboxDashBoardController.userEmail, findNodePathWithSeparator(mailbox.id, '.')!);
|
||||||
|
copySubaddressAction(context, subaddress);
|
||||||
|
} catch (error) {
|
||||||
|
appToast.showToastErrorMessage(context, AppLocalizations.of(context).errorWhileFetchingSubaddress);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case MailboxActions.disableSpamReport:
|
case MailboxActions.disableSpamReport:
|
||||||
case MailboxActions.enableSpamReport:
|
case MailboxActions.enableSpamReport:
|
||||||
mailboxDashBoardController.storeSpamReportStateAction();
|
mailboxDashBoardController.storeSpamReportStateAction();
|
||||||
@@ -1632,4 +1643,22 @@ class MailboxController extends BaseMailboxController
|
|||||||
|
|
||||||
return session.getContactSupportCapability(accountId);
|
return session.getContactSupportCapability(accountId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void copySubaddressAction(BuildContext context, String subaddress) {
|
||||||
|
Clipboard.setData(ClipboardData(text: subaddress));
|
||||||
|
appToast.showToastSuccessMessage(context, AppLocalizations.of(context).emailSubaddressCopiedToClipboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSubaddress(String userEmail, String folderName) {
|
||||||
|
if (folderName.isEmpty) {
|
||||||
|
throw EmptyFolderNameException(folderName);
|
||||||
|
}
|
||||||
|
|
||||||
|
final atIndex = userEmail.indexOf('@');
|
||||||
|
if (atIndex <= 0 || atIndex == userEmail.length - 1) {
|
||||||
|
throw InvalidMailFormatException(userEmail);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '${userEmail.substring(0, atIndex)}+$folderName@${userEmail.substring(atIndex + 1)}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -66,6 +66,8 @@ mixin MailboxWidgetMixin {
|
|||||||
MailboxActions.disallowSubaddressing
|
MailboxActions.disallowSubaddressing
|
||||||
else
|
else
|
||||||
MailboxActions.allowSubaddressing,
|
MailboxActions.allowSubaddressing,
|
||||||
|
if (mailbox.isSubaddressingAllowed)
|
||||||
|
MailboxActions.copySubaddress,
|
||||||
if (mailbox.isSubscribedMailbox)
|
if (mailbox.isSubscribedMailbox)
|
||||||
MailboxActions.disableMailbox
|
MailboxActions.disableMailbox
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ enum MailboxActions {
|
|||||||
markAsRead,
|
markAsRead,
|
||||||
selectForRuleAction,
|
selectForRuleAction,
|
||||||
openInNewTab,
|
openInNewTab,
|
||||||
|
copySubaddress,
|
||||||
disableSpamReport,
|
disableSpamReport,
|
||||||
enableSpamReport,
|
enableSpamReport,
|
||||||
disableMailbox,
|
disableMailbox,
|
||||||
@@ -50,6 +51,8 @@ extension MailboxActionsExtension on MailboxActions {
|
|||||||
switch(this) {
|
switch(this) {
|
||||||
case MailboxActions.openInNewTab:
|
case MailboxActions.openInNewTab:
|
||||||
return AppLocalizations.of(context).openInNewTab;
|
return AppLocalizations.of(context).openInNewTab;
|
||||||
|
case MailboxActions.copySubaddress:
|
||||||
|
return AppLocalizations.of(context).copySubaddress;
|
||||||
case MailboxActions.newSubfolder:
|
case MailboxActions.newSubfolder:
|
||||||
return AppLocalizations.of(context).newSubfolder;
|
return AppLocalizations.of(context).newSubfolder;
|
||||||
case MailboxActions.disableSpamReport:
|
case MailboxActions.disableSpamReport:
|
||||||
@@ -89,6 +92,8 @@ extension MailboxActionsExtension on MailboxActions {
|
|||||||
switch(this) {
|
switch(this) {
|
||||||
case MailboxActions.openInNewTab:
|
case MailboxActions.openInNewTab:
|
||||||
return imagePaths.icOpenInNewTab;
|
return imagePaths.icOpenInNewTab;
|
||||||
|
case MailboxActions.copySubaddress:
|
||||||
|
return imagePaths.icCopy;
|
||||||
case MailboxActions.newSubfolder:
|
case MailboxActions.newSubfolder:
|
||||||
return imagePaths.icAddNewFolder;
|
return imagePaths.icAddNewFolder;
|
||||||
case MailboxActions.disableSpamReport:
|
case MailboxActions.disableSpamReport:
|
||||||
@@ -181,6 +186,7 @@ extension MailboxActionsExtension on MailboxActions {
|
|||||||
ContextMenuItemState getContextMenuItemState(PresentationMailbox mailbox) {
|
ContextMenuItemState getContextMenuItemState(PresentationMailbox mailbox) {
|
||||||
switch(this) {
|
switch(this) {
|
||||||
case MailboxActions.openInNewTab:
|
case MailboxActions.openInNewTab:
|
||||||
|
case MailboxActions.copySubaddress:
|
||||||
case MailboxActions.newSubfolder:
|
case MailboxActions.newSubfolder:
|
||||||
case MailboxActions.disableSpamReport:
|
case MailboxActions.disableSpamReport:
|
||||||
case MailboxActions.enableSpamReport:
|
case MailboxActions.enableSpamReport:
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ class MailboxTree with EquatableMixin {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String? getNodePath(MailboxId mailboxId) {
|
String? getNodePath(MailboxId mailboxId, String pathSeparator) {
|
||||||
final matchedNode = findNode((node) => node.item.id == mailboxId);
|
final matchedNode = findNode((node) => node.item.id == mailboxId);
|
||||||
if (matchedNode == null) {
|
if (matchedNode == null) {
|
||||||
return null;
|
return null;
|
||||||
@@ -141,9 +141,9 @@ class MailboxTree with EquatableMixin {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (currentContext != null) {
|
if (currentContext != null) {
|
||||||
path = '${parentNode.item.getDisplayName(currentContext!)}/$path';
|
path = '${parentNode.item.getDisplayName(currentContext!)}$pathSeparator$path';
|
||||||
} else {
|
} else {
|
||||||
path = '${parentNode.item.name?.name}/$path';
|
path = '${parentNode.item.name?.name}$pathSeparator$path';
|
||||||
}
|
}
|
||||||
parentId = parentNode.item.parentId;
|
parentId = parentNode.item.parentId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -784,6 +784,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"emailSubaddressCopiedToClipboard": "Email subaddress copied to clipboard",
|
||||||
|
"@emailSubaddressCopiedToClipboard": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"minimize": "Minimize",
|
"minimize": "Minimize",
|
||||||
"@minimize": {
|
"@minimize": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -2592,6 +2598,12 @@
|
|||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"placeholders": {}
|
||||||
},
|
},
|
||||||
|
"copySubaddress": "Copy subaddress",
|
||||||
|
"@copySubaddress": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders_order": [],
|
||||||
|
"placeholders": {}
|
||||||
|
},
|
||||||
"regards": "Regards",
|
"regards": "Regards",
|
||||||
"@regards": {
|
"@regards": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
|
|||||||
@@ -800,6 +800,12 @@ class AppLocalizations {
|
|||||||
name: 'email_address_copied_to_clipboard');
|
name: 'email_address_copied_to_clipboard');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get emailSubaddressCopiedToClipboard {
|
||||||
|
return Intl.message(
|
||||||
|
'Email subaddress copied to clipboard',
|
||||||
|
name: 'emailSubaddressCopiedToClipboard');
|
||||||
|
}
|
||||||
|
|
||||||
String get minimize {
|
String get minimize {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'Minimize',
|
'Minimize',
|
||||||
@@ -2654,6 +2660,13 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get copySubaddress {
|
||||||
|
return Intl.message(
|
||||||
|
'Copy subaddress',
|
||||||
|
name: 'copySubaddress',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String get regards {
|
String get regards {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'Regards',
|
'Regards',
|
||||||
@@ -3005,6 +3018,13 @@ class AppLocalizations {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get errorWhileFetchingSubaddress {
|
||||||
|
return Intl.message(
|
||||||
|
'Error while fetching the subaddress',
|
||||||
|
name: 'errorWhileFetchingSubaddress',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
String get connectedToTheInternet {
|
String get connectedToTheInternet {
|
||||||
return Intl.message(
|
return Intl.message(
|
||||||
'Connected to the internet',
|
'Connected to the internet',
|
||||||
|
|||||||
+79
@@ -36,6 +36,8 @@ import 'package:tmail_ui_user/features/login/domain/usecases/delete_authority_oi
|
|||||||
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
|
import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart';
|
import 'package:tmail_ui_user/features/login/domain/usecases/get_authenticated_account_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart';
|
import 'package:tmail_ui_user/features/login/domain/usecases/update_account_cache_interactor.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/empty_folder_name_exception.dart';
|
||||||
|
import 'package:tmail_ui_user/features/mailbox/domain/exceptions/invalid_mail_format_exception.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_default_mailbox_interactor.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_default_mailbox_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbox_interactor.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbox_interactor.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart';
|
import 'package:tmail_ui_user/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart';
|
||||||
@@ -580,4 +582,81 @@ void main() {
|
|||||||
expect(spamId, isNull);
|
expect(spamId, isNull);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('getSubaddress:test', () {
|
||||||
|
setUp(() {
|
||||||
|
getEmailsInMailboxInteractor = MockGetEmailsInMailboxInteractor();
|
||||||
|
|
||||||
|
when(emailReceiveManager.pendingSharedFileInfo).thenAnswer((_) => BehaviorSubject.seeded([]));
|
||||||
|
|
||||||
|
Get.put(mailboxDashboardController);
|
||||||
|
mailboxDashboardController.onReady();
|
||||||
|
|
||||||
|
mailboxController = MailboxController(
|
||||||
|
createNewMailboxInteractor,
|
||||||
|
deleteMultipleMailboxInteractor,
|
||||||
|
renameMailboxInteractor,
|
||||||
|
moveMailboxInteractor,
|
||||||
|
subscribeMailboxInteractor,
|
||||||
|
subscribeMultipleMailboxInteractor,
|
||||||
|
subaddressingInteractor,
|
||||||
|
createDefaultMailboxInteractor,
|
||||||
|
treeBuilder,
|
||||||
|
verifyNameInteractor,
|
||||||
|
getAllMailboxInteractor,
|
||||||
|
refreshAllMailboxInteractor);
|
||||||
|
mailboxController.onReady();
|
||||||
|
|
||||||
|
threadController = ThreadController(
|
||||||
|
getEmailsInMailboxInteractor,
|
||||||
|
refreshChangesEmailsInMailboxInteractor,
|
||||||
|
loadMoreEmailsInMailboxInteractor,
|
||||||
|
searchEmailInteractor,
|
||||||
|
searchMoreEmailInteractor,
|
||||||
|
getEmailByIdInteractor);
|
||||||
|
Get.put(threadController);
|
||||||
|
|
||||||
|
advancedFilterController = AdvancedFilterController();
|
||||||
|
|
||||||
|
mailboxDashboardController.sessionCurrent = testSession;
|
||||||
|
mailboxDashboardController.filterMessageOption.value = FilterMessageOption.all;
|
||||||
|
mailboxDashboardController.accountId.value = testAccountId;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return subaddress with valid email and folder name', () {
|
||||||
|
const String userEmail = 'user@example.com';
|
||||||
|
const String folderName = 'folder';
|
||||||
|
final result = mailboxController.getSubaddress(userEmail, folderName);
|
||||||
|
|
||||||
|
expect(result, equals('user+folder@example.com'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error if empty local part', () {
|
||||||
|
const userEmail = '@example.com';
|
||||||
|
const folderName = 'folder';
|
||||||
|
|
||||||
|
expect(() => mailboxController.getSubaddress(userEmail, folderName), throwsA(isA<InvalidMailFormatException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error if empty folder name', () {
|
||||||
|
const userEmail = 'user@example.com';
|
||||||
|
const folderName = '';
|
||||||
|
|
||||||
|
expect(() => mailboxController.getSubaddress(userEmail, folderName), throwsA(isA<EmptyFolderNameException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error if empty domain', () {
|
||||||
|
const userEmail = 'user@';
|
||||||
|
const folderName = 'folder';
|
||||||
|
|
||||||
|
expect(() => mailboxController.getSubaddress(userEmail, folderName), throwsA(isA<InvalidMailFormatException>()));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should throw an error if absent `@`', () {
|
||||||
|
const userEmail = 'invalid-email-format';
|
||||||
|
const folderName = 'folder';
|
||||||
|
|
||||||
|
expect(() => mailboxController.getSubaddress(userEmail, folderName), throwsA(isA<InvalidMailFormatException>()));
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user