TF-3759 Update style popup context menu for filter message option on mailbox dashboard view

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-06-14 01:17:15 +07:00
committed by Dat H. Pham
parent b7e570293c
commit 506603b44f
7 changed files with 131 additions and 171 deletions
@@ -261,6 +261,7 @@ extension AppColor on Color {
static const gray686E76 = Color(0xFF686E76);
static const textSecondary = Color(0xFF1C1B1F);
static const profileMenuDivider = Color(0xFF1D192B);
static const popupMenuItemHovered = Color(0xFFF8F8F8);
static const mapGradientColor = [
[Color(0xFF21D4FD), Color(0xFFB721FF)],
@@ -76,6 +76,7 @@ class PopupMenuItemActionWidget extends StatelessWidget {
type: MaterialType.transparency,
child: InkWell(
onTap: () => menuAction.onClick(menuActionClick),
hoverColor: AppColor.popupMenuItemHovered,
child: Container(
height: 48,
width: double.infinity,
@@ -2,10 +2,8 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mixin/filter_email_popup_menu_mixin.dart';
abstract class BaseMailboxDashBoardView extends GetWidget<MailboxDashBoardController>
with FilterEmailPopupMenuMixin,
AppLoaderMixin {
with AppLoaderMixin {
BaseMailboxDashBoardView({Key? key}) : super(key: key);
}
@@ -8,6 +8,7 @@ import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:tmail_ui_user/features/base/widget/clean_messages_banner.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_no_icon_widget.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
import 'package:tmail_ui_user/features/base/widget/scrollbar_list_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/web/composer_overlay_view.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
@@ -42,9 +43,11 @@ import 'package:tmail_ui_user/features/quotas/presentation/widget/quotas_banner_
import 'package:tmail_ui_user/features/search/email/presentation/search_email_view.dart';
import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_view.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/popup_menu_item_filter_message_action.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_view.dart';
import 'package:tmail_ui_user/features/thread/presentation/widgets/spam_banner/spam_report_banner_web_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class MailboxDashBoardView extends BaseMailboxDashBoardView {
@@ -437,18 +440,30 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
FilterMessageOption filterMessageCurrent,
RelativeRect buttonPosition
) {
controller.openPopupMenuAction(
context,
buttonPosition,
popupMenuFilterEmailActionTile(
context,
filterMessageCurrent,
(filterMessageSelected) {
controller.dispatchAction(FilterMessageAction(filterMessageSelected));
},
isSearchEmailRunning: controller.searchController.isSearchEmailRunning
)
);
final popupMenuItems = [
if (!controller.searchController.isSearchEmailRunning)
FilterMessageOption.attachments,
FilterMessageOption.unread,
FilterMessageOption.starred,
].map((filterOption) {
return PopupMenuItem(
padding: EdgeInsets.zero,
child: PopupMenuItemActionWidget(
menuAction: PopupMenuItemFilterMessageAction(
filterOption,
filterMessageCurrent,
AppLocalizations.of(context),
controller.imagePaths,
),
menuActionClick: (menuAction) {
popBack();
controller.dispatchAction(FilterMessageAction(menuAction.action));
},
),
);
}).toList();
controller.openPopupMenuAction(context, buttonPosition, popupMenuItems);
}
void _onDeleteFilterMessageOptionAction() {
@@ -1,90 +0,0 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
mixin FilterEmailPopupMenuMixin {
final _imagePaths = Get.find<ImagePaths>();
List<PopupMenuEntry> popupMenuFilterEmailActionTile(
BuildContext context,
FilterMessageOption optionSelected,
Function(FilterMessageOption)? onCallBack,
{
bool isSearchEmailRunning = false
}
) {
return [
if (!isSearchEmailRunning)
...[
PopupMenuItem(
padding: EdgeInsets.zero,
child: _filterEmailAction(
context,
optionSelected,
FilterMessageOption.attachments,
onCallBack)),
const PopupMenuDivider(height: 0.5)
],
PopupMenuItem(
padding: EdgeInsets.zero,
child: _filterEmailAction(
context,
optionSelected,
FilterMessageOption.unread,
onCallBack)),
const PopupMenuDivider(height: 0.5),
PopupMenuItem(
padding: EdgeInsets.zero,
child: _filterEmailAction(
context,
optionSelected,
FilterMessageOption.starred,
onCallBack)),
];
}
Widget _filterEmailAction(
BuildContext context,
FilterMessageOption optionSelected,
FilterMessageOption option,
Function(FilterMessageOption)? onCallBack
) {
return InkWell(
onTap: () => onCallBack?.call(option),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16),
child: SizedBox(
child: Row(children: [
SvgPicture.asset(option.getContextMenuIcon(_imagePaths),
width: 20,
height: 20,
fit: BoxFit.fill,
colorFilter: option != FilterMessageOption.starred
? AppColor.colorTextButton.asFilter()
: null),
const SizedBox(width: 12),
Expanded(child: Text(
option.getName(AppLocalizations.of(context)),
style: const TextStyle(
fontSize: 15,
color: Colors.black,
fontWeight: FontWeight.w500))),
if (optionSelected == option)
... [
const SizedBox(width: 12),
SvgPicture.asset(_imagePaths.icFilterSelected,
width: 16,
height: 16,
fit: BoxFit.fill),
]
])
),
)
);
}
}
@@ -1,4 +1,3 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
@@ -7,57 +6,66 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/filter_message_button_style.dart';
import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart';
typedef OnSelectFilterMessageOptionAction = Function(
BuildContext context,
FilterMessageOption filterMessageOption,
RelativeRect buttonPosition);
typedef OnDeleteFilterMessageOptionAction = Function(FilterMessageOption filterMessageOption);
class FilterMessageButton extends StatelessWidget {
typedef OnSelectFilterMessageOptionAction = Function(BuildContext context,
FilterMessageOption filterMessageOption, RelativeRect buttonPosition);
typedef OnDeleteFilterMessageOptionAction = Function(
FilterMessageOption filterMessageOption);
class FilterMessageButton extends StatefulWidget {
static const int _titleCharactersMaximum = 20;
final FilterMessageOption filterMessageOption;
final bool isSelected;
final ImagePaths imagePaths;
final OnSelectFilterMessageOptionAction? onSelectFilterMessageOptionAction;
final OnDeleteFilterMessageOptionAction? onDeleteFilterMessageOptionAction;
final OnSelectFilterMessageOptionAction onSelectFilterMessageOptionAction;
final OnDeleteFilterMessageOptionAction onDeleteFilterMessageOptionAction;
const FilterMessageButton({
super.key,
required this.filterMessageOption,
required this.imagePaths,
this.isSelected = false,
this.onSelectFilterMessageOptionAction,
this.onDeleteFilterMessageOptionAction,
required this.onSelectFilterMessageOptionAction,
required this.onDeleteFilterMessageOptionAction,
});
@override
State<FilterMessageButton> createState() => _FilterMessageButtonState();
}
class _FilterMessageButtonState extends State<FilterMessageButton> {
final GlobalKey _buttonKey = GlobalKey();
@override
Widget build(BuildContext context) {
final filterMessageTitle = filterMessageOption.getTitle(context);
final filterMessageTitle = widget.filterMessageOption.getTitle(context);
final deleteButtonWidget = TMailButtonWidget.fromIcon(
icon: imagePaths.icDeleteSelection,
icon: widget.imagePaths.icDeleteSelection,
iconSize: FilterMessageButtonStyle.deleteIconSize,
iconColor: AppColor.colorFilterMessageIcon,
padding: const EdgeInsets.all(8),
backgroundColor: Colors.transparent,
onTapActionCallback: () => onDeleteFilterMessageOptionAction?.call(filterMessageOption),
onTapActionCallback: () => widget.onDeleteFilterMessageOptionAction
.call(widget.filterMessageOption),
);
final listComponentsWidget = [
SvgPicture.asset(
filterMessageOption.getIcon(imagePaths),
widget.filterMessageOption.getIcon(widget.imagePaths),
width: FilterMessageButtonStyle.iconSize,
height: FilterMessageButtonStyle.iconSize,
colorFilter: filterMessageOption.getIconColor().asFilter(),
fit: BoxFit.fill
colorFilter: widget.filterMessageOption.getIconColor().asFilter(),
fit: BoxFit.fill,
),
const SizedBox(width: FilterMessageButtonStyle.spaceSize),
Text(
filterMessageTitle.length > _titleCharactersMaximum
? '${filterMessageTitle.substring(0, _titleCharactersMaximum)}...'
: filterMessageTitle,
filterMessageTitle.length > FilterMessageButton._titleCharactersMaximum
? '${filterMessageTitle.substring(
0,
FilterMessageButton._titleCharactersMaximum,
)}...'
: filterMessageTitle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: FilterMessageButtonStyle.titleStyle,
@@ -65,78 +73,108 @@ class FilterMessageButton extends StatelessWidget {
Padding(
padding: FilterMessageButtonStyle.elementPadding,
child: SvgPicture.asset(
imagePaths.icDropDown,
widget.imagePaths.icDropDown,
width: FilterMessageButtonStyle.arrowDownIconSize,
height: FilterMessageButtonStyle.arrowDownIconSize,
colorFilter: AppColor.colorFilterMessageIcon.asFilter(),
fit: BoxFit.fill
fit: BoxFit.fill,
),
),
];
final childItem = _buildContainerForComponents(
children: [
... listComponentsWidget,
if (isSelected) deleteButtonWidget,
]
...listComponentsWidget,
if (widget.isSelected) deleteButtonWidget,
],
);
if (onSelectFilterMessageOptionAction == null) {
return childItem;
}
if (!isSelected) {
if (widget.isSelected) {
return _buildContainerForComponents(
key: _buttonKey,
children: [
InkWell(
onTapDown: (details) => _onTapDownAction(context, details),
borderRadius: FilterMessageButtonStyle.borderRadius,
child: Row(
mainAxisSize: MainAxisSize.min,
children: listComponentsWidget,
),
),
if (widget.isSelected) deleteButtonWidget,
],
);
} else {
return Material(
type: MaterialType.transparency,
child: InkWell(
key: _buttonKey,
onTapDown: (details) => _onTapDownAction(context, details),
borderRadius: FilterMessageButtonStyle.borderRadius,
child: childItem
child: childItem,
),
);
}
return _buildContainerForComponents(
children: [
InkWell(
onTapDown: (details) => _onTapDownAction(context, details),
borderRadius: FilterMessageButtonStyle.borderRadius,
child: Row(
mainAxisSize: MainAxisSize.min,
children: listComponentsWidget,
),
),
if (isSelected) deleteButtonWidget,
]
);
}
Widget _buildContainerForComponents({required List<Widget> children}) {
Widget _buildContainerForComponents({
required List<Widget> children,
Key? key,
}) {
return Container(
key: key,
decoration: BoxDecoration(
borderRadius: FilterMessageButtonStyle.borderRadius,
color: filterMessageOption.getBackgroundColor(isSelected: isSelected)
color: widget.filterMessageOption.getBackgroundColor(
isSelected: widget.isSelected,
),
),
padding: FilterMessageButtonStyle.getButtonPadding(isSelected),
padding: FilterMessageButtonStyle.getButtonPadding(widget.isSelected),
child: Row(
mainAxisSize: MainAxisSize.min,
children: children
)
children: children,
),
);
}
void _onTapDownAction(BuildContext context, TapDownDetails details) {
final screenSize = MediaQuery.sizeOf(context);
final offset = details.globalPosition;
final position = RelativeRect.fromLTRB(
offset.dx,
offset.dy,
screenSize.width - offset.dx,
screenSize.height - offset.dy);
RelativeRect? position;
onSelectFilterMessageOptionAction?.call(
final buttonBox =
_buttonKey.currentContext?.findRenderObject() as RenderBox?;
final overlayBox =
Overlay.maybeOf(context)?.context.findRenderObject() as RenderBox?;
if (buttonBox == null || overlayBox == null) {
final screenSize = MediaQuery.sizeOf(context);
final offset = details.globalPosition;
position = RelativeRect.fromLTRB(
offset.dx,
offset.dy,
screenSize.width - offset.dx,
screenSize.height - offset.dy,
);
} else {
final buttonPosition = buttonBox.localToGlobal(
Offset.zero,
ancestor: overlayBox,
);
final buttonSize = buttonBox.size;
position = RelativeRect.fromRect(
Rect.fromLTWH(
buttonPosition.dx,
buttonPosition.dy + buttonSize.height + 8,
0,
0,
),
Offset.zero & overlayBox.size,
);
}
widget.onSelectFilterMessageOptionAction.call(
context,
filterMessageOption,
position);
widget.filterMessageOption,
position,
);
}
}
}
@@ -17,7 +17,6 @@ import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_
import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.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/mixin/filter_email_popup_menu_mixin.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/widgets/recover_deleted_message_loading_banner_widget.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';
@@ -50,9 +49,7 @@ import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class ThreadView extends GetWidget<ThreadController>
with AppLoaderMixin,
FilterEmailPopupMenuMixin,
PopupMenuWidgetMixin {
with AppLoaderMixin, PopupMenuWidgetMixin {
ThreadView({Key? key}) : super(key: key);