TF-3764 Add Create filter button in context menu

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-06-12 11:48:20 +07:00
committed by Dat H. Pham
parent bab0331fe0
commit 5068a11524
19 changed files with 172 additions and 74 deletions
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
class PopupItemWidgetStyle {
static const double iconSize = 24;
static const double iconSize = 20;
static const double selectedIconSize = 16;
static const double space = 12;
static const double space = 16;
static const double height = 48;
static const double minWidth = 256;
@@ -10,6 +10,7 @@ class PopupItemWidget extends StatelessWidget {
final String nameAction;
final Color? colorIcon;
final double? iconSize;
final double? iconSpace;
final TextStyle? styleName;
final bool? isSelected;
final String? selectedIcon;
@@ -25,6 +26,7 @@ class PopupItemWidget extends StatelessWidget {
this.styleName,
this.isSelected,
this.padding,
this.iconSpace,
this.selectedIcon,
this.onCallbackAction,
}) : super(key: key);
@@ -48,7 +50,7 @@ class PopupItemWidget extends StatelessWidget {
fit: BoxFit.fill,
colorFilter: colorIcon?.asFilter()
),
const SizedBox(width: PopupItemWidgetStyle.space),
SizedBox(width: iconSpace ?? PopupItemWidgetStyle.space),
Expanded(child: Text(
nameAction,
style: styleName ?? PopupItemWidgetStyle.labelTextStyle
@@ -85,6 +85,7 @@ import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_ma
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/remove_email_drafts_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_create_filter_for_folder.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
@@ -1285,6 +1286,9 @@ class MailboxController extends BaseMailboxController
case MailboxActions.newSubfolder:
goToCreateNewMailboxView(context, parentMailbox: mailbox);
break;
case MailboxActions.createFilter:
mailboxDashBoardController.openCreateEmailRuleView(mailbox);
break;
case MailboxActions.recoverDeletedMessages:
mailboxDashBoardController.gotoEmailRecovery();
break;
@@ -27,6 +27,7 @@ mixin MailboxWidgetMixin {
MailboxActions.openInNewTab,
if (!mailbox.isRecovered)
MailboxActions.newSubfolder,
MailboxActions.createFilter,
if (mailbox.isTrash)
...[
MailboxActions.emptyTrash,
@@ -49,6 +50,7 @@ mixin MailboxWidgetMixin {
if (PlatformInfo.isWeb && mailbox.isSubscribedMailbox)
MailboxActions.openInNewTab,
MailboxActions.newSubfolder,
MailboxActions.createFilter,
if (mailbox.countUnReadEmailsAsString.isNotEmpty)
MailboxActions.markAsRead,
MailboxActions.move,
@@ -202,11 +204,8 @@ mixin MailboxWidgetMixin {
iconAction: contextMenuItem.action.getContextMenuIcon(imagePaths),
nameAction: contextMenuItem.action.getTitleContextMenu(context),
colorIcon: contextMenuItem.action.getColorContextMenuIcon(),
padding: const EdgeInsetsDirectional.only(start: 12),
iconSize: 24,
styleName: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
padding: const EdgeInsetsDirectional.symmetric(horizontal: 12),
styleName: ThemeUtils.textStyleBodyBody3(
color: contextMenuItem.action.getColorContextMenuTitle()
),
onCallbackAction: () => handleMailboxAction(context, contextMenuItem.action, mailbox)
@@ -24,6 +24,7 @@ enum MailboxActions {
emptyTrash,
emptySpam,
newSubfolder,
createFilter,
confirmMailSpam,
recoverDeletedMessages,
allowSubaddressing,
@@ -55,6 +56,8 @@ extension MailboxActionsExtension on MailboxActions {
return AppLocalizations.of(context).copySubaddress;
case MailboxActions.newSubfolder:
return AppLocalizations.of(context).newSubfolder;
case MailboxActions.createFilter:
return AppLocalizations.of(context).createFilter;
case MailboxActions.disableSpamReport:
return AppLocalizations.of(context).disableSpamReport;
case MailboxActions.enableSpamReport:
@@ -96,6 +99,8 @@ extension MailboxActionsExtension on MailboxActions {
return imagePaths.icCopy;
case MailboxActions.newSubfolder:
return imagePaths.icAddNewFolder;
case MailboxActions.createFilter:
return imagePaths.icCreateFilter;
case MailboxActions.disableSpamReport:
return imagePaths.icSpamReportDisable;
case MailboxActions.enableSpamReport:
@@ -132,7 +137,7 @@ extension MailboxActionsExtension on MailboxActions {
Color getColorContextMenuTitle() {
switch(this) {
case MailboxActions.delete:
return AppColor.colorActionDeleteConfirmDialog;
return AppColor.redFF3347;
default:
return Colors.black;
}
@@ -141,9 +146,9 @@ extension MailboxActionsExtension on MailboxActions {
Color getColorContextMenuIcon() {
switch(this) {
case MailboxActions.delete:
return AppColor.colorActionDeleteConfirmDialog;
return AppColor.redFF3347;
default:
return AppColor.primaryColor;
return AppColor.steelGrayA540;
}
}
@@ -188,6 +193,7 @@ extension MailboxActionsExtension on MailboxActions {
case MailboxActions.openInNewTab:
case MailboxActions.copySubaddress:
case MailboxActions.newSubfolder:
case MailboxActions.createFilter:
case MailboxActions.disableSpamReport:
case MailboxActions.enableSpamReport:
case MailboxActions.enableMailbox:
@@ -42,6 +42,7 @@ class FoldersBarWidget extends StatelessWidget {
backgroundColor: Colors.transparent,
iconColor: AppColor.steelGrayA540,
iconSize: 20,
padding: const EdgeInsets.all(5),
tooltipMessage: AppLocalizations.of(context).searchForFolders,
onTapActionCallback: onOpenSearchFolder,
);
@@ -57,7 +58,7 @@ class FoldersBarWidget extends StatelessWidget {
icon: imagePaths.icAddNewFolder,
backgroundColor: Colors.transparent,
iconColor: AppColor.steelGrayA540,
iconSize: 24,
iconSize: 20,
padding: const EdgeInsets.all(5),
tooltipMessage: AppLocalizations.of(context).newFolder,
onTapActionCallback: onAddNewFolder,
@@ -11,8 +11,8 @@ class MailboxBottomSheetActionTileBuilder extends CupertinoActionSheetActionBuil
final PresentationMailbox presentationMailbox;
final SvgPicture? actionSelected;
final Color? bgColor;
final EdgeInsets? iconLeftPadding;
final EdgeInsets? iconRightPadding;
final EdgeInsetsGeometry? iconLeftPadding;
final EdgeInsetsGeometry? iconRightPadding;
final bool opacity;
final bool absorbing;
@@ -46,8 +46,10 @@ class MailboxBottomSheetActionTileBuilder extends CupertinoActionSheetActionBuil
key: key,
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Padding(
padding: iconLeftPadding ?? const EdgeInsets.only(left: 12, right: 16),
child: actionIcon),
padding: iconLeftPadding ??
const EdgeInsetsDirectional.only(start: 12, end: 16),
child: actionIcon,
),
Expanded(child: Text(actionName, textAlign: TextAlign.left, style: actionTextStyle())),
]),
onPressed: () {
@@ -104,6 +104,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/spam_report_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/delete_emails_in_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_clear_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_create_filter_for_folder.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preferences_setting_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_save_email_as_draft_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
@@ -119,9 +120,11 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/sear
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/quick_search_filter.dart';
import 'package:tmail_ui_user/features/mailto/presentation/model/mailto_arguments.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_vacation_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/update_vacation_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_vacation_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/update_vacation_interactor.dart';
@@ -234,6 +237,7 @@ class MailboxDashBoardController extends ReloadableController
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
IOSNotificationManager? _iosNotificationManager;
GetServerSettingInteractor? getServerSettingInteractor;
CreateNewEmailRuleFilterInteractor? createNewEmailRuleFilterInteractor;
final scaffoldKey = GlobalKey<ScaffoldState>();
final selectedMailbox = Rxn<PresentationMailbox>();
@@ -461,6 +465,8 @@ class MailboxDashBoardController extends ReloadableController
isSenderImportantFlagEnabled.value = success.settingOption.isDisplaySenderPriority;
} else if (success is ClearMailboxSuccess) {
clearMailboxSuccess(success);
} else if (success is CreateNewRuleFilterSuccess) {
handleCreateNewRuleFilterSuccess(success);
}
}
@@ -499,6 +505,8 @@ class MailboxDashBoardController extends ReloadableController
_handleGetAllIdentitiesFailure();
} else if (failure is ClearMailboxFailure) {
clearMailboxFailure(failure);
} else if (failure is CreateNewRuleFilterFailure) {
handleCreateNewRuleFilterFailure(failure);
}
}
@@ -0,0 +1,86 @@
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/manage_account/domain/exceptions/rule_filter_exception.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/dialog_router.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension HandleCreateFilterForFolder on MailboxDashBoardController {
Future<void> openCreateEmailRuleView(
PresentationMailbox presentationMailbox,
) async {
final accountId = this.accountId.value;
final session = sessionCurrent;
if (accountId == null) {
consumeState(Stream.value(
Left(CreateNewRuleFilterFailure(NotFoundAccountIdException()))));
return;
}
if (session == null) {
consumeState(Stream.value(
Left(CreateNewRuleFilterFailure(NotFoundSessionException()))));
return;
}
final arguments = RulesFilterCreatorArguments(
accountId,
session,
mailboxDestination: presentationMailbox,
);
final newRuleFilterRequest = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(
routeName: AppRoutes.rulesFilterCreator,
arguments: arguments,
)
: await push(
AppRoutes.rulesFilterCreator,
arguments: arguments,
);
if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) {
_createNewRuleFilterAction(accountId, newRuleFilterRequest);
}
}
void _createNewRuleFilterAction(
AccountId accountId,
CreateNewEmailRuleFilterRequest ruleFilterRequest,
) {
createNewEmailRuleFilterInteractor =
getBinding<CreateNewEmailRuleFilterInteractor>();
if (createNewEmailRuleFilterInteractor == null) {
consumeState(Stream.value(
Left(CreateNewRuleFilterFailure(RuleFilterNotBindingException()))));
return;
}
consumeState(createNewEmailRuleFilterInteractor!.execute(
accountId,
ruleFilterRequest,
));
}
void handleCreateNewRuleFilterSuccess(CreateNewRuleFilterSuccess success) {
if (success.newListRules.isNotEmpty == true) {
toastManager.showMessageSuccess(success);
}
}
void handleCreateNewRuleFilterFailure(CreateNewRuleFilterFailure failure) {
toastManager.showMessageFailure(failure);
}
}
@@ -0,0 +1,2 @@
class RuleFilterNotBindingException implements Exception {}
@@ -34,14 +34,10 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/search_email_filter.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart';
import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart';
import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart';
import 'package:tmail_ui_user/features/thread/domain/model/email_filter.dart';
@@ -70,9 +66,7 @@ import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_t
import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/dialog_router.dart';
import 'package:tmail_ui_user/main/routes/navigation_router.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/routes/route_utils.dart';
@@ -93,8 +87,6 @@ class ThreadController extends BaseController with EmailActionController {
final GetEmailByIdInteractor _getEmailByIdInteractor;
final CleanAndGetEmailsInMailboxInteractor cleanAndGetEmailsInMailboxInteractor;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
final listEmailDrag = <PresentationEmail>[].obs;
bool _rangeSelectionMode = false;
final openingEmail = RxBool(false);
@@ -195,8 +187,6 @@ class ThreadController extends BaseController with EmailActionController {
_openEmailWithoutMailboxFromLocationBar(success.email);
}
}
} else if (success is CreateNewRuleFilterSuccess) {
_createNewRuleFilterSuccess(success);
}
}
@@ -1384,47 +1374,6 @@ class ThreadController extends BaseController with EmailActionController {
mailboxDashBoardController.onDragMailbox(isDrag);
}
void goToCreateEmailRuleView() async {
if (_accountId != null && _session != null) {
final arguments = RulesFilterCreatorArguments(
_accountId!,
_session!,
mailboxDestination: selectedMailbox
);
final newRuleFilterRequest = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.rulesFilterCreator, arguments: arguments)
: await push(AppRoutes.rulesFilterCreator, arguments: arguments);
if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) {
_createNewRuleFilterAction(_accountId!, newRuleFilterRequest);
}
} else {
logError('ThreadController::goToCreateEmailRuleView: Account or Session is NULL');
}
}
void _createNewRuleFilterAction(
AccountId accountId,
CreateNewEmailRuleFilterRequest ruleFilterRequest
) async {
_createNewEmailRuleFilterInteractor = getBinding<CreateNewEmailRuleFilterInteractor>();
if (_createNewEmailRuleFilterInteractor != null) {
consumeState(_createNewEmailRuleFilterInteractor!.execute(accountId, ruleFilterRequest));
}
}
void _createNewRuleFilterSuccess(CreateNewRuleFilterSuccess success) {
if (success.newListRules.isNotEmpty == true &&
currentOverlayContext != null &&
currentContext != null) {
appToast.showToastSuccessMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).newFilterWasCreated
);
}
}
Future<bool> swipeEmailAction(BuildContext context, PresentationEmail email, DismissDirection direction) async {
if (direction == DismissDirection.startToEnd) {
ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread;
+14 -2
View File
@@ -1,5 +1,5 @@
{
"@@last_modified": "2025-06-02T16:27:17.653715",
"@@last_modified": "2025-06-02T18:48:11.159281",
"initializing_data": "Initializing data...",
"@initializing_data": {
"type": "text",
@@ -4528,7 +4528,7 @@
"placeholders_order": [],
"placeholders": {}
},
"youDoNotHaveAnyEmailInYourCurrentFolder": "You dont have any emails in this folder.",
"youDoNotHaveAnyEmailInYourCurrentFolder": "You dont have any emails\n in this folder.",
"@youDoNotHaveAnyEmailInYourCurrentFolder": {
"type": "text",
"placeholders_order": [],
@@ -4539,5 +4539,17 @@
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"createFilter": "Create filter",
"@createFilter": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
},
"createFilterRuleFailed": "Create filter rule failed",
"@createFilterRuleFailed": {
"type": "text",
"placeholders_order": [],
"placeholders": {}
}
}
@@ -4780,4 +4780,18 @@ class AppLocalizations {
name: 'startToComposeEmails',
);
}
String get createFilter {
return Intl.message(
'Create filter',
name: 'createFilter',
);
}
String get createFilterRuleFailed {
return Intl.message(
'Create filter rule failed',
name: 'createFilterRuleFailed',
);
}
}
+5
View File
@@ -25,6 +25,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.
import 'package:tmail_ui_user/features/manage_account/domain/state/add_recipient_in_forwarding_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipient_in_forwarding_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/edit_local_copy_in_forwarding_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/export_trace_log_state.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_up_twake_workplace_state.dart';
@@ -198,6 +199,8 @@ class ToastManager {
} else {
message = AppLocalizations.of(currentContext!).deleteRecipientsFailed;
}
} else if (failure is CreateNewRuleFilterFailure) {
message = AppLocalizations.of(currentContext!).createFilterRuleFailed;
}
if (message?.isNotEmpty == true) {
@@ -219,6 +222,8 @@ class ToastManager {
message = AppLocalizations.of(currentContext!).toast_message_empty_trash_folder_success;
} else if (success is ExportTraceLogSuccess) {
message = AppLocalizations.of(currentContext!).exportTraceLogSuccess(success.savePath);
} else if (success is CreateNewRuleFilterSuccess) {
message = AppLocalizations.of(currentContext!).newFilterWasCreated;
}
if (message?.isNotEmpty == true) {